def read_db_keywords(self, db_path): """Can we read sqlite keywords with the generic readKeywords method """ # noinspection PyUnresolvedReferences local_path = os.path.join( os.path.dirname(__file__), '../../..///', 'jk.sqlite') self.keyword_io.set_keyword_db_path(db_path) # We need to make a local copy of the dataset so # that we can use a local path that will hash properly on the # database to return us the correct / valid keywords record. shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), local_path) uri = QgsDataSourceURI() # always use relative path! uri.setDatabase('../jk.sqlite') uri.setDataSource('', 'osm_buildings', 'Geometry') # create a local version that has the relative url sqlite_layer = QgsVectorLayer(uri.uri(), 'OSM Buildings', 'spatialite') expected_source = ( 'dbname=\'../jk.sqlite\' table="osm_buildings" (Geometry) sql=') message = 'Got source: %s\n\nExpected %s\n' % ( sqlite_layer.source(), expected_source) assert sqlite_layer.source() == expected_source, message keywords = self.keyword_io.read_keywords(sqlite_layer) expected_keywords = self.expected_sqlite_keywords message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, self.sqlite_layer.source()) assert keywords == expected_keywords, message source = self.sqlite_layer.source() # delete sqlite_layer so that we can delete the file del sqlite_layer os.remove(local_path) message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) assert keywords == expected_keywords, message
def read_db_keywords(self, db_path): """Can we read sqlite keywords with the generic readKeywords method """ self.keyword_io.set_keyword_db_path(db_path) # We need to use relative path so that the hash from URI will match local_path = os.path.join( os.path.dirname(__file__), 'exposure.sqlite') sqlite_building_path = test_data_path('exposure', 'exposure.sqlite') shutil.copy2(sqlite_building_path, local_path) uri = QgsDataSourceURI() uri.setDatabase('exposure.sqlite') uri.setDataSource('', 'buildings_osm_4326', 'Geometry') sqlite_layer = QgsVectorLayer(uri.uri(), 'OSM Buildings', 'spatialite') expected_source = ( 'dbname=\'exposure.sqlite\' table="buildings_osm_4326" (' 'Geometry) sql=') message = 'Got source: %s\n\nExpected %s\n' % ( sqlite_layer.source(), expected_source) self.assertEqual(sqlite_layer.source(), expected_source, message) keywords = self.keyword_io.read_keywords(sqlite_layer) expected_keywords = self.expected_sqlite_keywords message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, self.sqlite_layer.source()) self.assertDictEqual(keywords, expected_keywords, message) # Delete SQL Layer so that we can delete the file del sqlite_layer os.remove(local_path)
def testUniqueSource(self): """ Similar memory layers should have unique source - some code checks layer source to identify matching layers """ layer = QgsVectorLayer("Point", "test", "memory") layer2 = QgsVectorLayer("Point", "test2", "memory") self.assertNotEqual(layer.source(), layer2.source())
def getObjectFromUri(uri, forceLoad=True): """Returns an object (layer/table) given a source definition. if forceLoad is true, it tries to load it if it is not currently open Otherwise, it will return the object only if it is loaded in QGIS. """ if uri is None: return None if uri in _loadedLayers: return _loadedLayers[uri] layers = getRasterLayers() for layer in layers: if normalizeLayerSource(layer.source()) == normalizeLayerSource(uri): return layer layers = getVectorLayers() for layer in layers: if normalizeLayerSource(layer.source()) == normalizeLayerSource(uri): return layer tables = getTables() for table in tables: if normalizeLayerSource(table.source()) == normalizeLayerSource(uri): return table if forceLoad: settings = QSettings() prjSetting = settings.value('/Projections/defaultBehaviour') settings.setValue('/Projections/defaultBehaviour', '') # If is not opened, we open it layer = QgsVectorLayer(uri, uri, 'ogr') if layer.isValid(): if prjSetting: settings.setValue('/Projections/defaultBehaviour', prjSetting) _loadedLayers[normalizeLayerSource(layer.source())] = layer return layer layer = QgsVectorLayer(uri, uri, 'postgres') if layer.isValid(): if prjSetting: settings.setValue('/Projections/defaultBehaviour', prjSetting) _loadedLayers[normalizeLayerSource(layer.source())] = layer return layer layer = QgsRasterLayer(uri, uri) if layer.isValid(): if prjSetting: settings.setValue('/Projections/defaultBehaviour', prjSetting) _loadedLayers[normalizeLayerSource(layer.source())] = layer return layer if prjSetting: settings.setValue('/Projections/defaultBehaviour', prjSetting) else: return None
def test_readDBKeywords(self): """Can we read sqlite keywords with the generic readKeywords method """ myLocalPath = os.path.join(os.path.dirname(__file__), '..', 'jk.sqlite') myPath = os.path.join(TESTDATA, 'test_keywords.db') self.keywordIO.setKeywordDbPath(myPath) # We need to make a local copy of the dataset so # that we can use a local path that will hash properly on the # database to return us the correct / valid keywords record. shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), myLocalPath) myUri = QgsDataSourceURI() # always use relative path! myUri.setDatabase('../jk.sqlite') myUri.setDataSource('', 'osm_buildings', 'Geometry') # create a local version that has the relative url mySqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings', 'spatialite') myExpectedSource = ('dbname=\'../jk.sqlite\' table="osm_buildings"' ' (Geometry) sql=') myMessage = 'Got source: %s\n\nExpected %s\n' % ( mySqliteLayer.source, myExpectedSource) assert mySqliteLayer.source() == myExpectedSource, myMessage myKeywords = self.keywordIO.readKeywords(mySqliteLayer) myExpectedKeywords = self.expectedSqliteKeywords assert myKeywords == myExpectedKeywords, myMessage mySource = self.sqliteLayer.source() os.remove(myLocalPath) myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage
def load_wfs_layer(url, name): """Helper to load wfs layer and load it as QGIS layer. :param url: The complete URL to the WFS layer. :type url: str :param name: The layer name. :type name: str :returns: Layer instance. :rtype: QgsMapLayer """ layer = QgsVectorLayer(url, name, 'WFS') # noinspection PyUnresolvedReferences message = 'Layer "%s" is not valid' % layer.source() # noinspection PyUnresolvedReferences if not layer.isValid(): print message # noinspection PyUnresolvedReferences if not layer.isValid(): raise Exception(message) return layer
class KeywordIOTest(unittest.TestCase): """Tests for reading and writing of raster and vector data """ def setUp(self): self.keywordIO = KeywordIO() myUri = QgsDataSourceURI() myUri.setDatabase(os.path.join(TESTDATA, 'jk.sqlite')) myUri.setDataSource('', 'osm_buildings', 'Geometry') self.sqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings', 'spatialite') myHazardPath = os.path.join(HAZDATA, 'Shakemap_Padang_2009.asc') self.fileRasterLayer, myType = loadLayer(myHazardPath, theDirectory=None) del myType self.fileVectorLayer, myType = loadLayer('Padang_WGS84.shp') del myType self.expectedSqliteKeywords = {'category': 'exposure', 'datatype': 'OSM', 'subcategory': 'building'} self.expectedVectorKeywords = {'category': 'exposure', 'datatype': 'itb', 'subcategory': 'structure'} self.expectedRasterKeywords = {'category': 'hazard', 'source': 'USGS', 'subcategory': 'earthquake', 'unit': 'MMI', 'title': ('An earthquake in Padang ' 'like in 2009')} def tearDown(self): pass def test_getHashForDatasource(self): """Test we can reliably get a hash for a uri""" myHash = self.keywordIO.getHashForDatasource(PG_URI) myExpectedHash = '7cc153e1b119ca54a91ddb98a56ea95e' myMessage = "Got: %s\nExpected: %s" % (myHash, myExpectedHash) assert myHash == myExpectedHash, myMessage def test_writeReadKeywordFromUri(self): """Test we can set and get keywords for a non local datasource""" myHandle, myFilename = tempfile.mkstemp('.db', 'keywords_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(myHandle) os.remove(myFilename) myExpectedKeywords = {'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keywordIO.setKeywordDbPath(myFilename) self.keywordIO.writeKeywordsForUri(PG_URI, myExpectedKeywords) # SQL Update test # On second write schema is populated and we update matching hash myExpectedKeywords = {'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keywordIO.writeKeywordsForUri(PG_URI, myExpectedKeywords) # Test getting all keywords myKeywords = self.keywordIO.readKeywordFromUri(PG_URI) myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( myKeywords, myExpectedKeywords, myFilename) assert myKeywords == myExpectedKeywords, myMessage # Test getting just a single keyword myKeyword = self.keywordIO.readKeywordFromUri(PG_URI, 'datatype') myExpectedKeyword = 'OSM' myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( myKeyword, myExpectedKeyword, myFilename) assert myKeyword == myExpectedKeyword, myMessage # Test deleting keywords actually does delete self.keywordIO.deleteKeywordsForUri(PG_URI) try: myKeyword = self.keywordIO.readKeywordFromUri(PG_URI, 'datatype') #if the above didnt cause an exception then bad myMessage = 'Expected a HashNotFoundException to be raised' assert myMessage except HashNotFoundException: #we expect this outcome so good! pass def test_areKeywordsFileBased(self): """Can we correctly determine if keywords should be written to file or to database?""" assert not self.keywordIO.areKeywordsFileBased(self.sqliteLayer) assert self.keywordIO.areKeywordsFileBased(self.fileRasterLayer) assert self.keywordIO.areKeywordsFileBased(self.fileVectorLayer) def test_readRasterFileKeywords(self): """Can we read raster file keywords using generic readKeywords method """ myKeywords = self.keywordIO.readKeywords(self.fileRasterLayer) myExpectedKeywords = self.expectedRasterKeywords mySource = self.fileRasterLayer.source() myMessage = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage def test_readVectorFileKeywords(self): """Can we read vector file keywords with the generic readKeywords method """ myKeywords = self.keywordIO.readKeywords(self.fileVectorLayer) myExpectedKeywords = self.expectedVectorKeywords mySource = self.fileVectorLayer.source() myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage def test_appendKeywords(self): """Can we append file keywords with the generic readKeywords method.""" myLayer, _ = makePadangLayerClone() myNewKeywords = {'category': 'exposure', 'test': 'TEST'} self.keywordIO.appendKeywords(myLayer, myNewKeywords) myKeywords = self.keywordIO.readKeywords(myLayer) for myKey, myValue in myNewKeywords.iteritems(): myMessage = ('Layer keywords misses appended key: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (myKey, myKeywords, myNewKeywords)) assert myKey in myKeywords, myMessage myMessage = ('Layer keywords misses appended value: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (myValue, myKeywords, myNewKeywords)) assert myKeywords[myKey] == myValue, myMessage def test_readDBKeywords(self): """Can we read sqlite keywords with the generic readKeywords method """ myLocalPath = os.path.join(os.path.dirname(__file__), '..', 'jk.sqlite') myPath = os.path.join(TESTDATA, 'test_keywords.db') self.keywordIO.setKeywordDbPath(myPath) # We need to make a local copy of the dataset so # that we can use a local path that will hash properly on the # database to return us the correct / valid keywords record. shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), myLocalPath) myUri = QgsDataSourceURI() # always use relative path! myUri.setDatabase('../jk.sqlite') myUri.setDataSource('', 'osm_buildings', 'Geometry') # create a local version that has the relative url mySqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings', 'spatialite') myExpectedSource = ('dbname=\'../jk.sqlite\' table="osm_buildings"' ' (Geometry) sql=') myMessage = 'Got source: %s\n\nExpected %s\n' % ( mySqliteLayer.source, myExpectedSource) assert mySqliteLayer.source() == myExpectedSource, myMessage myKeywords = self.keywordIO.readKeywords(mySqliteLayer) myExpectedKeywords = self.expectedSqliteKeywords assert myKeywords == myExpectedKeywords, myMessage mySource = self.sqliteLayer.source() os.remove(myLocalPath) myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage
class KeywordIOTest(unittest.TestCase): """Tests for reading and writing of raster and vector data """ def setUp(self): self.keyword_io = KeywordIO() # SQLite Layer uri = QgsDataSourceURI() sqlite_building_path = test_data_path('exposure', 'exposure.sqlite') uri.setDatabase(sqlite_building_path) uri.setDataSource('', 'buildings_osm_4326', 'Geometry') self.sqlite_layer = QgsVectorLayer( uri.uri(), 'OSM Buildings', 'spatialite') self.expected_sqlite_keywords = { 'category': 'exposure', 'datatype': 'OSM', 'subcategory': 'building'} # Raster Layer keywords hazard_path = test_data_path('hazard', 'tsunami_wgs84.tif') self.raster_layer, _ = load_layer(hazard_path) self.expected_raster_keywords = { 'hazard_category': 'single_event', 'title': 'Tsunami', 'hazard': 'tsunami', 'continuous_hazard_unit': 'metres', 'layer_geometry': 'raster', 'layer_purpose': 'hazard', 'layer_mode': 'continuous', 'keyword_version': inasafe_keyword_version } # Vector Layer keywords vector_path = test_data_path('exposure', 'buildings_osm_4326.shp') self.vector_layer, _ = load_layer(vector_path) self.expected_vector_keywords = { 'keyword_version': inasafe_keyword_version, 'structure_class_field': 'FLOODED', 'title': 'buildings_osm_4326', 'layer_geometry': 'polygon', 'layer_purpose': 'exposure', 'layer_mode': 'classified', 'exposure': 'structure' } # Keyword less layer keywordless_path = test_data_path('other', 'keywordless_layer.shp') self.keywordless_layer, _ = load_layer(keywordless_path) def tearDown(self): pass def test_get_hash_for_datasource(self): """Test we can reliably get a hash for a uri""" hash_value = self.keyword_io.hash_for_datasource(PG_URI) expected_hash = '7cc153e1b119ca54a91ddb98a56ea95e' message = "Got: %s\nExpected: %s" % (hash_value, expected_hash) self.assertEqual(hash_value, expected_hash, message) def test_write_read_keyword_from_uri(self): """Test we can set and get keywords for a non local datasource""" handle, filename = tempfile.mkstemp( '.db', 'keywords_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(handle) os.remove(filename) expected_keywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keyword_io.set_keyword_db_path(filename) self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # SQL Update test # On second write schema is populated and we update matching hash expected_keywords = { 'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # Test getting all keywords keywords = self.keyword_io.read_keyword_from_uri(PG_URI) message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keywords, expected_keywords, filename) self.assertDictEqual(keywords, expected_keywords, message) # Test getting just a single keyword keyword = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') expected_keyword = 'OSM' message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keyword, expected_keyword, filename) self.assertDictEqual(keywords, expected_keywords, message) # Test deleting keywords actually does delete self.keyword_io.delete_keywords_for_uri(PG_URI) try: _ = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') # if the above didn't cause an exception then bad message = 'Expected a HashNotFoundError to be raised' assert message except HashNotFoundError: # we expect this outcome so good! pass def test_are_keywords_file_based(self): """Can we correctly determine if keywords should be written to file or to database?""" assert not self.keyword_io.are_keywords_file_based(self.sqlite_layer) assert self.keyword_io.are_keywords_file_based(self.raster_layer) assert self.keyword_io.are_keywords_file_based(self.vector_layer) def test_read_raster_file_keywords(self): """Can we read raster file keywords using generic readKeywords method """ keywords = self.keyword_io.read_keywords(self.raster_layer) expected_keywords = self.expected_raster_keywords source = self.raster_layer.source() message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( keywords, expected_keywords, source) self.assertDictEqual(keywords, expected_keywords, message) def test_read_vector_file_keywords(self): """Test read vector file keywords with the generic readKeywords method. """ keywords = self.keyword_io.read_keywords(self.vector_layer) expected_keywords = self.expected_vector_keywords source = self.vector_layer.source() message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) self.assertDictEqual(keywords, expected_keywords, message) def test_read_keywordless_layer(self): """Test read 'keyword' file from keywordless layer. """ self.assertRaises( NoKeywordsFoundError, self.keyword_io.read_keywords, self.keywordless_layer, ) def test_update_keywords(self): """Test append file keywords with update_keywords method.""" layer = clone_raster_layer( name='tsunami_wgs84', extension='.tif', include_keywords=True, source_directory=test_data_path('hazard')) new_keywords = {'category': 'exposure', 'test': 'TEST'} self.keyword_io.update_keywords(layer, new_keywords) keywords = self.keyword_io.read_keywords(layer) expected_keywords = { 'category': 'exposure', 'hazard_category': 'single_event', 'title': 'Tsunami', 'hazard': 'tsunami', 'continuous_hazard_unit': 'metres', 'test': 'TEST', 'layer_geometry': 'raster', 'layer_purpose': 'hazard', 'layer_mode': 'continuous', 'keyword_version': inasafe_keyword_version } message = 'Got:\n%s\nExpected:\n%s' % (keywords, expected_keywords) self.assertDictEqual(keywords, expected_keywords, message) def test_read_db_keywords(self): """Can we read sqlite kw with the generic read_keywords method """ db_path = test_data_path('other', 'test_keywords.db') self.read_db_keywords(db_path) def read_db_keywords(self, db_path): """Can we read sqlite keywords with the generic readKeywords method """ self.keyword_io.set_keyword_db_path(db_path) # We need to use relative path so that the hash from URI will match local_path = os.path.join( os.path.dirname(__file__), 'exposure.sqlite') sqlite_building_path = test_data_path('exposure', 'exposure.sqlite') shutil.copy2(sqlite_building_path, local_path) uri = QgsDataSourceURI() uri.setDatabase('exposure.sqlite') uri.setDataSource('', 'buildings_osm_4326', 'Geometry') sqlite_layer = QgsVectorLayer(uri.uri(), 'OSM Buildings', 'spatialite') expected_source = ( 'dbname=\'exposure.sqlite\' table="buildings_osm_4326" (' 'Geometry) sql=') message = 'Got source: %s\n\nExpected %s\n' % ( sqlite_layer.source(), expected_source) self.assertEqual(sqlite_layer.source(), expected_source, message) keywords = self.keyword_io.read_keywords(sqlite_layer) expected_keywords = self.expected_sqlite_keywords message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, self.sqlite_layer.source()) self.assertDictEqual(keywords, expected_keywords, message) # Delete SQL Layer so that we can delete the file del sqlite_layer os.remove(local_path) def test_copy_keywords(self): """Test we can copy the keywords.""" out_path = unique_filename( prefix='test_copy_keywords', suffix='.keywords') self.keyword_io.copy_keywords(self.raster_layer, out_path) copied_keywords = read_file_keywords(out_path) expected_keywords = self.expected_raster_keywords message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( copied_keywords, expected_keywords, out_path) self.assertDictEqual(copied_keywords, expected_keywords, message) def test_definition(self): """Test we can get definitions for keywords. .. versionadded:: 3.2 """ keyword = 'hazards' definition = self.keyword_io.definition(keyword) self.assertTrue('description' in definition) def test_to_message(self): """Test we can convert keywords to a message object. .. versionadded:: 3.2 """ keywords = self.keyword_io.read_keywords(self.vector_layer) message = self.keyword_io.to_message(keywords).to_text() self.assertIn('*Exposure*, structure------', message) def test_dict_to_row(self): """Test the dict to row helper works. .. versionadded:: 3.2 """ keyword_value = ( "{'high': ['Kawasan Rawan Bencana III'], " "'medium': ['Kawasan Rawan Bencana II'], " "'low': ['Kawasan Rawan Bencana I']}") table = self.keyword_io._dict_to_row(keyword_value) self.assertIn( u'\n---\n*high*, Kawasan Rawan Bencana III------', table.to_text()) # should also work passing a dict keyword_value = { 'high': ['Kawasan Rawan Bencana III'], 'medium': ['Kawasan Rawan Bencana II'], 'low': ['Kawasan Rawan Bencana I']} table = self.keyword_io._dict_to_row(keyword_value) self.assertIn( u'\n---\n*high*, Kawasan Rawan Bencana III------', table.to_text())
class KeywordIOTest(unittest.TestCase): """Tests for reading and writing of raster and vector data """ def setUp(self): self.keyword_io = KeywordIO() uri = QgsDataSourceURI() uri.setDatabase(os.path.join(TESTDATA, 'jk.sqlite')) uri.setDataSource('', 'osm_buildings', 'Geometry') self.sqlite_layer = QgsVectorLayer( uri.uri(), 'OSM Buildings', 'spatialite') hazard_path = os.path.join(HAZDATA, 'Shakemap_Padang_2009.asc') self.raster_layer, layer_type = load_layer( hazard_path, directory=None) del layer_type self.vector_layer, layer_type = load_layer('Padang_WGS84.shp') del layer_type self.expected_sqlite_keywords = { 'category': 'exposure', 'datatype': 'OSM', 'subcategory': 'building'} self.expected_vector_keywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'structure', 'title': 'Padang WGS84'} self.expected_raster_keywords = { 'category': 'hazard', 'source': 'USGS', 'subcategory': 'earthquake', 'unit': 'MMI', 'title': ('An earthquake in Padang ' 'like in 2009')} def tearDown(self): pass def test_get_hash_for_datasource(self): """Test we can reliably get a hash for a uri""" hash_value = self.keyword_io.hash_for_datasource(PG_URI) expected_hash = '7cc153e1b119ca54a91ddb98a56ea95e' message = "Got: %s\nExpected: %s" % (hash_value, expected_hash) assert hash_value == expected_hash, message def test_write_read_keyword_from_uri(self): """Test we can set and get keywords for a non local datasource""" handle, filename = tempfile.mkstemp( '.db', 'keywords_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(handle) os.remove(filename) expected_keywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keyword_io.set_keyword_db_path(filename) self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # SQL Update test # On second write schema is populated and we update matching hash expected_keywords = { 'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # Test getting all keywords keywords = self.keyword_io.read_keyword_from_uri(PG_URI) message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keywords, expected_keywords, filename) assert keywords == expected_keywords, message # Test getting just a single keyword keyword = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') expected_keyword = 'OSM' message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keyword, expected_keyword, filename) assert keyword == expected_keyword, message # Test deleting keywords actually does delete self.keyword_io.delete_keywords_for_uri(PG_URI) try: _ = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') #if the above didnt cause an exception then bad message = 'Expected a HashNotFoundError to be raised' assert message except HashNotFoundError: #we expect this outcome so good! pass def test_are_keywords_file_based(self): """Can we correctly determine if keywords should be written to file or to database?""" assert not self.keyword_io.are_keywords_file_based(self.sqlite_layer) assert self.keyword_io.are_keywords_file_based(self.raster_layer) assert self.keyword_io.are_keywords_file_based(self.vector_layer) def test_read_raster_file_keywords(self): """Can we read raster file keywords using generic readKeywords method """ keywords = self.keyword_io.read_keywords(self.raster_layer) expected_keywords = self.expected_raster_keywords source = self.raster_layer.source() message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( keywords, expected_keywords, source) self.assertEquals(keywords, expected_keywords, message) def test_read_vector_file_keywords(self): """Test read vector file keywords with the generic readKeywords method. """ keywords = self.keyword_io.read_keywords(self.vector_layer) expected_keywords = self.expected_vector_keywords source = self.vector_layer.source() message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) assert keywords == expected_keywords, message def test_append_keywords(self): """Can we append file keywords with the generic readKeywords method.""" layer, _ = clone_padang_layer() new_keywords = {'category': 'exposure', 'test': 'TEST'} self.keyword_io.update_keywords(layer, new_keywords) keywords = self.keyword_io.read_keywords(layer) for key, value in new_keywords.iteritems(): message = ( 'Layer keywords misses appended key: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (key, keywords, new_keywords)) assert key in keywords, message message = ( 'Layer keywords misses appended value: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (value, keywords, new_keywords)) assert keywords[key] == value, message def test_read_db_keywords(self): """Can we read sqlite keywords with the generic readKeywords method """ # noinspection PyUnresolvedReferences local_path = os.path.join( os.path.dirname(__file__), '../../..///', 'jk.sqlite') path = os.path.join(TESTDATA, 'test_keywords.db') self.keyword_io.set_keyword_db_path(path) # We need to make a local copy of the dataset so # that we can use a local path that will hash properly on the # database to return us the correct / valid keywords record. shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), local_path) uri = QgsDataSourceURI() # always use relative path! uri.setDatabase('../jk.sqlite') uri.setDataSource('', 'osm_buildings', 'Geometry') # create a local version that has the relative url sqlite_layer = QgsVectorLayer(uri.uri(), 'OSM Buildings', 'spatialite') expected_source = ( 'dbname=\'../jk.sqlite\' table="osm_buildings" (Geometry) sql=') message = 'Got source: %s\n\nExpected %s\n' % ( sqlite_layer.source, expected_source) assert sqlite_layer.source() == expected_source, message keywords = self.keyword_io.read_keywords(sqlite_layer) expected_keywords = self.expected_sqlite_keywords assert keywords == expected_keywords, message source = self.sqlite_layer.source() # delete sqlite_layer so that we can delete the file del sqlite_layer os.remove(local_path) message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) assert keywords == expected_keywords, message def test_copy_keywords(self): """Test we can copy the keywords.""" out_path = unique_filename( prefix='test_copy_keywords', suffix='.keywords') self.keyword_io.copy_keywords(self.raster_layer, out_path) copied_keywords = read_file_keywords(out_path) expected_keywords = self.expected_raster_keywords message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( copied_keywords, expected_keywords, out_path) self.assertEquals(copied_keywords, expected_keywords, message)
class KeywordIOTest(unittest.TestCase): """Tests for reading and writing of raster and vector data """ def setUp(self): self.keyword_io = KeywordIO() uri = QgsDataSourceURI() uri.setDatabase(os.path.join(TESTDATA, 'jk.sqlite')) uri.setDataSource('', 'osm_buildings', 'Geometry') self.sqlite_layer = QgsVectorLayer( uri.uri(), 'OSM Buildings', 'spatialite') hazard_path = os.path.join(HAZDATA, 'Shakemap_Padang_2009.asc') self.raster_layer, layer_type = load_layer( hazard_path, directory=None) del layer_type self.vector_layer, layer_type = load_layer('Padang_WGS84.shp') del layer_type self.expected_sqlite_keywords = { 'category': 'exposure', 'datatype': 'OSM', 'subcategory': 'building'} self.expected_vector_keywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'structure', 'title': 'Padang WGS84'} self.expected_raster_keywords = { 'category': 'hazard', 'source': 'USGS', 'subcategory': 'earthquake', 'unit': 'MMI', 'title': ('An earthquake in Padang ' 'like in 2009')} def tearDown(self): pass def test_get_hash_for_datasource(self): """Test we can reliably get a hash for a uri""" hash_value = self.keyword_io.hash_for_datasource(PG_URI) expected_hash = '7cc153e1b119ca54a91ddb98a56ea95e' message = "Got: %s\nExpected: %s" % (hash_value, expected_hash) assert hash_value == expected_hash, message def test_write_read_keyword_from_uri(self): """Test we can set and get keywords for a non local datasource""" handle, filename = tempfile.mkstemp( '.db', 'keywords_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(handle) os.remove(filename) expected_keywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keyword_io.set_keyword_db_path(filename) self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # SQL Update test # On second write schema is populated and we update matching hash expected_keywords = { 'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # Test getting all keywords keywords = self.keyword_io.read_keyword_from_uri(PG_URI) message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keywords, expected_keywords, filename) assert keywords == expected_keywords, message # Test getting just a single keyword keyword = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') expected_keyword = 'OSM' message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keyword, expected_keyword, filename) assert keyword == expected_keyword, message # Test deleting keywords actually does delete self.keyword_io.delete_keywords_for_uri(PG_URI) try: _ = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') # if the above didnt cause an exception then bad message = 'Expected a HashNotFoundError to be raised' assert message except HashNotFoundError: # we expect this outcome so good! pass def test_are_keywords_file_based(self): """Can we correctly determine if keywords should be written to file or to database?""" assert not self.keyword_io.are_keywords_file_based(self.sqlite_layer) assert self.keyword_io.are_keywords_file_based(self.raster_layer) assert self.keyword_io.are_keywords_file_based(self.vector_layer) def test_read_raster_file_keywords(self): """Can we read raster file keywords using generic readKeywords method """ keywords = self.keyword_io.read_keywords(self.raster_layer) expected_keywords = self.expected_raster_keywords source = self.raster_layer.source() message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( keywords, expected_keywords, source) self.assertEquals(keywords, expected_keywords, message) def test_read_vector_file_keywords(self): """Test read vector file keywords with the generic readKeywords method. """ keywords = self.keyword_io.read_keywords(self.vector_layer) expected_keywords = self.expected_vector_keywords source = self.vector_layer.source() message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) assert keywords == expected_keywords, message def test_append_keywords(self): """Can we append file keywords with the generic readKeywords method.""" layer, _ = clone_padang_layer() new_keywords = {'category': 'exposure', 'test': 'TEST'} self.keyword_io.update_keywords(layer, new_keywords) keywords = self.keyword_io.read_keywords(layer) for key, value in new_keywords.iteritems(): message = ( 'Layer keywords misses appended key: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (key, keywords, new_keywords)) assert key in keywords, message message = ( 'Layer keywords misses appended value: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (value, keywords, new_keywords)) assert keywords[key] == value, message def test_read_db_keywords(self): """Can we read sqlite kw with the generic readKeywords method """ db_path = os.path.join(TESTDATA, 'test_keywords.db') self.read_db_keywords(db_path) def test_read_legacy_db_keywords(self): """Can we read legacy sqlite kw with the generic readKeywords method """ db_path = os.path.join(TESTDATA, 'test_keywords_legacy.db') self.read_db_keywords(db_path) def read_db_keywords(self, db_path): """Can we read sqlite keywords with the generic readKeywords method """ # noinspection PyUnresolvedReferences local_path = os.path.join( os.path.dirname(__file__), '../../..///', 'jk.sqlite') self.keyword_io.set_keyword_db_path(db_path) # We need to make a local copy of the dataset so # that we can use a local path that will hash properly on the # database to return us the correct / valid keywords record. shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), local_path) uri = QgsDataSourceURI() # always use relative path! uri.setDatabase('../jk.sqlite') uri.setDataSource('', 'osm_buildings', 'Geometry') # create a local version that has the relative url sqlite_layer = QgsVectorLayer(uri.uri(), 'OSM Buildings', 'spatialite') expected_source = ( 'dbname=\'../jk.sqlite\' table="osm_buildings" (Geometry) sql=') message = 'Got source: %s\n\nExpected %s\n' % ( sqlite_layer.source(), expected_source) assert sqlite_layer.source() == expected_source, message keywords = self.keyword_io.read_keywords(sqlite_layer) expected_keywords = self.expected_sqlite_keywords message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, self.sqlite_layer.source()) assert keywords == expected_keywords, message source = self.sqlite_layer.source() # delete sqlite_layer so that we can delete the file del sqlite_layer os.remove(local_path) message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) assert keywords == expected_keywords, message def test_copy_keywords(self): """Test we can copy the keywords.""" out_path = unique_filename( prefix='test_copy_keywords', suffix='.keywords') self.keyword_io.copy_keywords(self.raster_layer, out_path) copied_keywords = read_file_keywords(out_path) expected_keywords = self.expected_raster_keywords message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( copied_keywords, expected_keywords, out_path) self.assertEquals(copied_keywords, expected_keywords, message)
class KeywordIOTest(unittest.TestCase): """Tests for reading and writing of raster and vector data """ def setUp(self): self.keywordIO = KeywordIO() myUri = QgsDataSourceURI() myUri.setDatabase(os.path.join(TESTDATA, 'jk.sqlite')) myUri.setDataSource('', 'osm_buildings', 'Geometry') self.sqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings', 'spatialite') myHazardPath = os.path.join(HAZDATA, 'Shakemap_Padang_2009.asc') self.fileRasterLayer, myType = load_layer( myHazardPath, directory=None) del myType self.fileVectorLayer, myType = load_layer('Padang_WGS84.shp') del myType self.expectedSqliteKeywords = { 'category': 'exposure', 'datatype': 'OSM', 'subcategory': 'building'} self.expectedVectorKeywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'structure', 'title': 'Padang WGS84'} self.expectedRasterKeywords = { 'category': 'hazard', 'source': 'USGS', 'subcategory': 'earthquake', 'unit': 'MMI', 'title': ('An earthquake in Padang ' 'like in 2009')} def tearDown(self): pass def test_getHashForDatasource(self): """Test we can reliably get a hash for a uri""" myHash = self.keywordIO.hash_for_datasource(PG_URI) myExpectedHash = '7cc153e1b119ca54a91ddb98a56ea95e' myMessage = "Got: %s\nExpected: %s" % (myHash, myExpectedHash) assert myHash == myExpectedHash, myMessage def test_writeReadKeywordFromUri(self): """Test we can set and get keywords for a non local datasource""" myHandle, myFilename = tempfile.mkstemp('.db', 'keywords_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(myHandle) os.remove(myFilename) myExpectedKeywords = {'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keywordIO.set_keyword_db_path(myFilename) self.keywordIO.write_keywords_for_uri(PG_URI, myExpectedKeywords) # SQL Update test # On second write schema is populated and we update matching hash myExpectedKeywords = {'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keywordIO.write_keywords_for_uri(PG_URI, myExpectedKeywords) # Test getting all keywords myKeywords = self.keywordIO.read_keyword_from_uri(PG_URI) myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( myKeywords, myExpectedKeywords, myFilename) assert myKeywords == myExpectedKeywords, myMessage # Test getting just a single keyword myKeyword = self.keywordIO.read_keyword_from_uri(PG_URI, 'datatype') myExpectedKeyword = 'OSM' myMessage = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( myKeyword, myExpectedKeyword, myFilename) assert myKeyword == myExpectedKeyword, myMessage # Test deleting keywords actually does delete self.keywordIO.delete_keywords_for_uri(PG_URI) try: myKeyword = self.keywordIO.read_keyword_from_uri(PG_URI, 'datatype') #if the above didnt cause an exception then bad myMessage = 'Expected a HashNotFoundError to be raised' assert myMessage except HashNotFoundError: #we expect this outcome so good! pass def test_areKeywordsFileBased(self): """Can we correctly determine if keywords should be written to file or to database?""" assert not self.keywordIO.are_keywords_file_based(self.sqliteLayer) assert self.keywordIO.are_keywords_file_based(self.fileRasterLayer) assert self.keywordIO.are_keywords_file_based(self.fileVectorLayer) def test_readRasterFileKeywords(self): """Can we read raster file keywords using generic readKeywords method """ myKeywords = self.keywordIO.read_keywords(self.fileRasterLayer) myExpectedKeywords = self.expectedRasterKeywords mySource = self.fileRasterLayer.source() myMessage = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage def test_readVectorFileKeywords(self): """Test read vector file keywords with the generic readKeywords method. """ myKeywords = self.keywordIO.read_keywords(self.fileVectorLayer) myExpectedKeywords = self.expectedVectorKeywords mySource = self.fileVectorLayer.source() myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage def test_appendKeywords(self): """Can we append file keywords with the generic readKeywords method.""" myLayer, _ = makePadangLayerClone() myNewKeywords = {'category': 'exposure', 'test': 'TEST'} self.keywordIO.update_keywords(myLayer, myNewKeywords) myKeywords = self.keywordIO.read_keywords(myLayer) for myKey, myValue in myNewKeywords.iteritems(): myMessage = ( 'Layer keywords misses appended key: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (myKey, myKeywords, myNewKeywords)) assert myKey in myKeywords, myMessage myMessage = ( 'Layer keywords misses appended value: %s\n' 'Layer keywords:\n%s\n' 'Appended keywords:\n%s\n' % (myValue, myKeywords, myNewKeywords)) assert myKeywords[myKey] == myValue, myMessage def test_readDBKeywords(self): """Can we read sqlite keywords with the generic readKeywords method """ myLocalPath = os.path.join(os.path.dirname(__file__), '../../..///', 'jk.sqlite') myPath = os.path.join(TESTDATA, 'test_keywords.db') self.keywordIO.set_keyword_db_path(myPath) # We need to make a local copy of the dataset so # that we can use a local path that will hash properly on the # database to return us the correct / valid keywords record. shutil.copy2(os.path.join(TESTDATA, 'jk.sqlite'), myLocalPath) myUri = QgsDataSourceURI() # always use relative path! myUri.setDatabase('../jk.sqlite') myUri.setDataSource('', 'osm_buildings', 'Geometry') # create a local version that has the relative url mySqliteLayer = QgsVectorLayer(myUri.uri(), 'OSM Buildings', 'spatialite') myExpectedSource = ('dbname=\'../jk.sqlite\' table="osm_buildings"' ' (Geometry) sql=') myMessage = 'Got source: %s\n\nExpected %s\n' % ( mySqliteLayer.source, myExpectedSource) assert mySqliteLayer.source() == myExpectedSource, myMessage myKeywords = self.keywordIO.read_keywords(mySqliteLayer) myExpectedKeywords = self.expectedSqliteKeywords assert myKeywords == myExpectedKeywords, myMessage mySource = self.sqliteLayer.source() # delete mySqliteLayer so that we can delete the file del mySqliteLayer os.remove(myLocalPath) myMessage = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( myKeywords, myExpectedKeywords, mySource) assert myKeywords == myExpectedKeywords, myMessage
class KeywordIOTest(unittest.TestCase): """Tests for reading and writing of raster and vector data """ def setUp(self): self.keyword_io = KeywordIO() # SQLite Layer uri = QgsDataSourceURI() sqlite_building_path = test_data_path('exposure', 'exposure.sqlite') uri.setDatabase(sqlite_building_path) uri.setDataSource('', 'buildings_osm_4326', 'Geometry') self.sqlite_layer = QgsVectorLayer( uri.uri(), 'OSM Buildings', 'spatialite') self.expected_sqlite_keywords = { 'category': 'exposure', 'datatype': 'OSM', 'subcategory': 'building'} # Raster Layer keywords hazard_path = test_data_path('hazard', 'tsunami_wgs84.tif') self.raster_layer, _ = load_layer(hazard_path) self.expected_raster_keywords = { 'category': 'hazard', 'subcategory': 'tsunami', 'data_type': 'continuous', 'unit': 'metres_depth', 'title': 'Tsunami'} # Vector Layer keywords vector_path = test_data_path('exposure', 'buildings_osm_4326.shp') self.vector_layer, _ = load_layer(vector_path) self.expected_vector_keywords = { 'category': 'exposure', 'datatype': 'osm', 'subcategory': 'structure', 'title': 'buildings_osm_4326', 'purpose': 'dki'} # Keyword less layer keywordless_path = test_data_path('other', 'keywordless_layer.shp') self.keywordless_layer, _ = load_layer(keywordless_path) def tearDown(self): pass def test_get_hash_for_datasource(self): """Test we can reliably get a hash for a uri""" hash_value = self.keyword_io.hash_for_datasource(PG_URI) expected_hash = '7cc153e1b119ca54a91ddb98a56ea95e' message = "Got: %s\nExpected: %s" % (hash_value, expected_hash) assert hash_value == expected_hash, message def test_write_read_keyword_from_uri(self): """Test we can set and get keywords for a non local datasource""" handle, filename = tempfile.mkstemp( '.db', 'keywords_', temp_dir()) # Ensure the file is deleted before we try to write to it # fixes windows specific issue where you get a message like this # ERROR 1: c:\temp\inasafe\clip_jpxjnt.shp is not a directory. # This is because mkstemp creates the file handle and leaves # the file open. os.close(handle) os.remove(filename) expected_keywords = { 'category': 'exposure', 'datatype': 'itb', 'subcategory': 'building'} # SQL insert test # On first write schema is empty and there is no matching hash self.keyword_io.set_keyword_db_path(filename) self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # SQL Update test # On second write schema is populated and we update matching hash expected_keywords = { 'category': 'exposure', 'datatype': 'OSM', # <--note the change here! 'subcategory': 'building'} self.keyword_io.write_keywords_for_uri(PG_URI, expected_keywords) # Test getting all keywords keywords = self.keyword_io.read_keyword_from_uri(PG_URI) message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keywords, expected_keywords, filename) assert keywords == expected_keywords, message # Test getting just a single keyword keyword = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') expected_keyword = 'OSM' message = 'Got: %s\n\nExpected %s\n\nDB: %s' % ( keyword, expected_keyword, filename) assert keyword == expected_keyword, message # Test deleting keywords actually does delete self.keyword_io.delete_keywords_for_uri(PG_URI) try: _ = self.keyword_io.read_keyword_from_uri(PG_URI, 'datatype') # if the above didnt cause an exception then bad message = 'Expected a HashNotFoundError to be raised' assert message except HashNotFoundError: # we expect this outcome so good! pass def test_are_keywords_file_based(self): """Can we correctly determine if keywords should be written to file or to database?""" assert not self.keyword_io.are_keywords_file_based(self.sqlite_layer) assert self.keyword_io.are_keywords_file_based(self.raster_layer) assert self.keyword_io.are_keywords_file_based(self.vector_layer) def test_read_raster_file_keywords(self): """Can we read raster file keywords using generic readKeywords method """ keywords = self.keyword_io.read_keywords(self.raster_layer) expected_keywords = self.expected_raster_keywords source = self.raster_layer.source() message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( keywords, expected_keywords, source) self.assertEquals(keywords, expected_keywords, message) def test_read_vector_file_keywords(self): """Test read vector file keywords with the generic readKeywords method. """ keywords = self.keyword_io.read_keywords(self.vector_layer) expected_keywords = self.expected_vector_keywords source = self.vector_layer.source() message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, source) assert keywords == expected_keywords, message def test_read_keywordless_layer(self): """Test read 'keyword' file from keywordless layer. """ self.assertRaises( NoKeywordsFoundError, self.keyword_io.read_keywords, self.keywordless_layer, ) def test_update_keywords(self): """Test append file keywords with update_keywords method.""" layer = clone_raster_layer( name='tsunami_wgs84', extension='.tif', include_keywords=True, source_directory=test_data_path('hazard')) new_keywords = {'category': 'exposure', 'test': 'TEST'} self.keyword_io.update_keywords(layer, new_keywords) keywords = self.keyword_io.read_keywords(layer) expected_keywords = { 'category': 'exposure', 'subcategory': 'tsunami', 'data_type': 'continuous', 'title': 'Tsunami', 'test': 'TEST', 'unit': 'metres_depth' } message = 'Keywords: %s. Expected: %s' % (keywords, expected_keywords) self.assertEqual(keywords, expected_keywords, message) def test_read_db_keywords(self): """Can we read sqlite kw with the generic read_keywords method """ db_path = test_data_path('other', 'test_keywords.db') self.read_db_keywords(db_path) def read_db_keywords(self, db_path): """Can we read sqlite keywords with the generic readKeywords method """ self.keyword_io.set_keyword_db_path(db_path) # We need to use relative path so that the hash from URI will match local_path = os.path.join( os.path.dirname(__file__), 'exposure.sqlite') sqlite_building_path = test_data_path('exposure', 'exposure.sqlite') shutil.copy2(sqlite_building_path, local_path) uri = QgsDataSourceURI() uri.setDatabase('exposure.sqlite') uri.setDataSource('', 'buildings_osm_4326', 'Geometry') sqlite_layer = QgsVectorLayer(uri.uri(), 'OSM Buildings', 'spatialite') expected_source = ( 'dbname=\'exposure.sqlite\' table="buildings_osm_4326" (' 'Geometry) sql=') message = 'Got source: %s\n\nExpected %s\n' % ( sqlite_layer.source(), expected_source) self.assertEqual(sqlite_layer.source(), expected_source, message) keywords = self.keyword_io.read_keywords(sqlite_layer) expected_keywords = self.expected_sqlite_keywords message = 'Got: %s\n\nExpected %s\n\nSource: %s' % ( keywords, expected_keywords, self.sqlite_layer.source()) self.assertEqual(keywords, expected_keywords, message) # Delete SQL Layer so that we can delete the file del sqlite_layer os.remove(local_path) def test_copy_keywords(self): """Test we can copy the keywords.""" out_path = unique_filename( prefix='test_copy_keywords', suffix='.keywords') self.keyword_io.copy_keywords(self.raster_layer, out_path) copied_keywords = read_file_keywords(out_path) expected_keywords = self.expected_raster_keywords message = 'Got:\n%s\nExpected:\n%s\nSource:\n%s' % ( copied_keywords, expected_keywords, out_path) self.assertEquals(copied_keywords, expected_keywords, message)
class Dialog(QDialog, Ui_Dialog): sampItems = {} # {name1 : [layer1, [field_src,field_dsn,Active?], [field_src,field_dsn,Active?], ...] , name2 : [layer2, ...] } polyItems = {} # {name1 : [layer1, [field_src,field_dsn,Active?], [field_src,field_dsn,Active?], ...] , name2 : [layer2, ...] } rastItems = {} # {name1 : [layer1, [band_name,field_dsn,Active?], [band_name,field_dsn,Active?], ...] , name2 : [layer2, ...] } fields = [] # [[type,layer,field],[type,layer,field],[type,layer,field]...] list of adresses of output fields def __init__(self, iface): QDialog.__init__(self) self.iface = iface self.setupUi(self) self.outButton.clicked.connect(self.outFile) self.inSample.currentIndexChanged.connect(self.updateFieldsList) self.inData.itemSelectionChanged.connect(self.updateFieldsTable) self.fieldsTable.cellChanged.connect(self.fieldNameChanged) self.addToMapCanvas.setCheckState(Qt.Checked) mapCanvas = self.iface.mapCanvas() # init dictionaries of items: self.sampItems = {} self.polyItems = {} self.rastItems = {} for i in range(mapCanvas.layerCount()): layer = mapCanvas.layer(i) if ( layer.type() == layer.VectorLayer ) and ( layer.geometryType() == QgsWkbTypes.PointGeometry ): # read point layers provider = layer.dataProvider() fields = provider.fields() theItem = [layer] for j in fields: theItem += [[str(j.name()), str(j.name()), False]] self.sampItems[str(layer.name())] = theItem self.inSample.addItem(layer.name()) elif ( layer.type() == layer.VectorLayer ) and ( layer.geometryType() == QgsWkbTypes.PolygonGeometry ): # read polygon layers provider = layer.dataProvider() fields = provider.fields() theItem = [layer] for j in fields: theItem += [[str(j.name()), str(j.name()), False]] self.polyItems[str(layer.name())] = theItem elif layer.type() == layer.RasterLayer: # read raster layers theItem = [layer] for j in range(layer.bandCount()): if layer.bandCount() == 1: name1 = layer.bandName(j+1) name2 = layer.name()[:10] else: name1 = layer.bandName(j+1) name2 = layer.name()[:8] + "_" + str(j+1) theItem += [[name1, name2, False]] self.rastItems[str(layer.name())] = theItem self.updateFieldsList() def updateFieldsList(self): self.inData.clear() if not self.inSample.count(): return i = self.inSample.currentText() for j in range(1, len(self.sampItems[i])): #clear previously enabled fields (as they aren't selected in the widget) self.sampItems[i][j][2] = False self.inData.addItem(self.sampItems[i][0].name() + " : " + self.sampItems[i][j][0] + " (source point)") #NOT YET FINISHED - to be switched to tree rather # self.inData.addItem(str(self.sampItems[i][0].name()) + " (X coordinate)") # self.inData.addItem(str(self.sampItems[i][0].name()) + " (Y coordinate)") for i in self.polyItems: for j in range(1, len(self.polyItems[i])): self.inData.addItem(str(self.polyItems[i][0].name()) + " : " + str(self.polyItems[i][j][0]) + " (polygon)") for i in self.rastItems: for j in range(1, len(self.rastItems[i])): self.inData.addItem(str(self.rastItems[i][0].name()) + " : "+ str(self.rastItems[i][j][0]) + " (raster)") self.updateFieldsTable() self.repaint() def updateFieldsTable(self): # called after selection changing # mark selected point items n=0 i = self.inSample.currentText() for j in range(1, len(self.sampItems[i])): if self.inData.item(n) and self.inData.item(n).isSelected(): self.sampItems[i][j][2] = True else: self.sampItems[i][j][2] = False n += 1 # mark selected polygon items for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.inData.item(n) and self.inData.item(n).isSelected(): self.polyItems[i][j][2] = True else: self.polyItems[i][j][2] = False n += 1 # mark selected raster items (don't zero n; it's one list) for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.inData.item(n) and self.inData.item(n).isSelected(): self.rastItems[i][j][2] = True else: self.rastItems[i][j][2] = False n += 1 # fill the fieldsTable with point, then polygon and then raster items: self.fields = [] n = 0 self.fieldsTable.setRowCount(0) i = self.inSample.currentText() for j in range(1, len(self.sampItems[i])): if self.sampItems[i][j][2]: self.fields += [["point",i,j]] self.fieldsTable.setRowCount(n+1) cell = QTableWidgetItem(str(self.sampItems[i][0].name()) + " : " + str(self.sampItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.fieldsTable.setItem(n,0,cell) self.fieldsTable.setItem(n,1,QTableWidgetItem(str(self.sampItems[i][j][1]))) n += 1 for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.polyItems[i][j][2]: self.fields += [["poly",i,j]] self.fieldsTable.setRowCount(n+1) cell = QTableWidgetItem(str(self.polyItems[i][0].name()) + " : " + str(self.polyItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.fieldsTable.setItem(n,0,cell) self.fieldsTable.setItem(n,1,QTableWidgetItem(str(self.polyItems[i][j][1]))) n += 1 for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.rastItems[i][j][2]: self.fields += [["rast",i,j]] self.fieldsTable.setRowCount(n+1) cell = QTableWidgetItem(str(self.rastItems[i][0].name()) + " : " + str(self.rastItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.fieldsTable.setItem(n,0,cell) self.fieldsTable.setItem(n,1,QTableWidgetItem(str(self.rastItems[i][j][1]))) n += 1 self.fieldsTable.resizeColumnsToContents() def fieldNameChanged(self, n): # called when any cell of the fieldsTable was modyfied # exit when false alarm if len(self.fields) == 0: return 0 if self.fieldsTable.rowCount() == 0: return 0 updatedItem = self.fieldsTable.item(n,1) if updatedItem == None: return 0 # update items dictionaries updatedText = str(updatedItem.text()) if self.fields[n][0] == "point": self.sampItems[self.fields[n][1]][self.fields[n][2]][1] = updatedText[:10] elif self.fields[n][0] == "poly": self.polyItems[self.fields[n][1]][self.fields[n][2]][1] = updatedText[:10] else: self.rastItems[self.fields[n][1]][self.fields[n][2]][1] = updatedText[:10] # cut to 10 characters if exceed if len(updatedText) > 10: self.updateFieldsTable() QMessageBox.information(self, self.tr("Point Sampling Tool"), self.tr("Name length can't exceed 10 chars, so it has been truncated.")) # This message box may to make some confusion, if user press OK while "too long name" is still under edition. # In this case, the message box pops up (correctly) and then the OK button becomes pressed, but the self.accept method is not called. # This pressed button with no action may to look like a hang up. # I've no idea either # how to pull this button up, or # how to forse execution the self.accept method, or # how to don't allow user to exceed 10 chars limit in QTableWidget cell. (THIS OPTION WOULD BE THE BEST!) self.fieldsTable.resizeColumnsToContents() def outFile(self): # by Carson Farmer 2008 # display file dialog for output file self.outShape.clear() outName, _ = QFileDialog().getSaveFileName(self, self.tr("Output file"), ".", self.tr("GeoPackages(*.gpkg);;Comma separated values (*.csv);;Shapefiles (*.shp)"), options = QFileDialog.DontConfirmOverwrite) outPath = QFileInfo(outName).absoluteFilePath() if not outPath.upper().endswith('.GPKG') and not outPath.upper().endswith('.CSV') and not outPath.upper().endswith('.SHP'): outPath += '.gpkg' if outName: self.outShape.clear() self.outShape.insert(outPath) def accept(self): # Called when "OK" button pressed (based on the Carson Farmer's PointsInPoly Plugin, 2008) # check if all fields are filled up self.statusLabel.setText(self.tr("Check input values, please!")) nothingSelected = True for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.polyItems[i][j][2]: nothingSelected = False for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.rastItems[i][j][2]: nothingSelected = False if self.inSample.currentText() == "": self.tabWidget.setCurrentIndex(0) QMessageBox.information(self, self.tr("Point Sampling Tool"), self.tr("Please select vector layer containing the sampling points")) return if nothingSelected: self.tabWidget.setCurrentIndex(0) QMessageBox.information(self, self.tr("Point Sampling Tool"), self.tr("Please select at least one polygon attribute or raster band")) return if self.outShape.text() == "": self.tabWidget.setCurrentIndex(0) QMessageBox.information(self, self.tr("Point Sampling Tool"), self.tr("Please specify output file name")) return # check if destination field names are unique if not self.testFieldsNames(self.fields): self.updateFieldsTable() self.tabWidget.setCurrentIndex(1) QMessageBox.warning(self, self.tr("Point Sampling Tool"), self.tr("At least two field names are the same!\nPlease type unique names.")) return # Check if there a CRS mismatch pointLayerSrid = list(self.sampItems.values())[0][0].crs().postgisSrid() msg = self.tr('''<html>All layers must have the same coordinate refere system. The <b>%s</b> layer seems to have different CRS id (<b>%d</b>) than the point layer (<b>%d</b>). If they are two different CRSes, you need to reproject one of the layers first, otherwise results will be wrong.<br/> However, if you are sure both CRSes are the same, and they are just improperly recognized, you can safely continue. Do you want to continue?</html>''') for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.polyItems[i][j][2]: layerSrid = self.polyItems[i][0].crs().postgisSrid() if layerSrid != pointLayerSrid: if QMessageBox.question(self, self.tr("Point Sampling Tool: layer CRS mismatch!"), msg % (i, layerSrid, pointLayerSrid), QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.rastItems[i][j][2]: layerSrid = self.rastItems[i][0].crs().postgisSrid() if layerSrid != pointLayerSrid: if QMessageBox.question(self, self.tr("Point Sampling Tool: layer CRS mismatch!"), msg % (i, layerSrid, pointLayerSrid), QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return if True: # all tests passed! Let's go on self.statusLabel.setText(self.tr("Processing the output file name...")) self.repaint() outPath = self.outShape.text() outPath = outPath.replace("\\","/") if not outPath.upper().endswith('.GPKG') and not outPath.upper().endswith('.CSV') and not outPath.upper().endswith('.SHP'): outPath += '.gpkg' outName = QFileInfo(outPath).fileName() tableName = None oldFile = QFile(outPath) if oldFile.exists(): if not outPath.upper().endswith('.GPKG'): if QMessageBox.question(self, self.tr("Point Sampling Tool"), self.tr("File %s already exists. Do you want to overwrite?") % outName) == QMessageBox.No: # return to filling the input fields self.outShape.clear() self.statusLabel.setText(self.tr("Fill up the input fields, please.")) self.repaint() return else: msg = self.tr("""Please provide <b>table name</b> for your layer.<br/> <b>WARNING: </b>Database %s already exists. If you select a table existing in it, the table will be overwritten.""") % outName tableName, result = QInputDialog.getText(self, "Point Sampling Tool", msg, text=outName[:-5]) if not result: # return to filling the input fields self.outShape.clear() self.statusLabel.setText(self.tr("Fill up the input fields, please.")) self.repaint() return self.statusLabel.setText(self.tr("Processing...")) self.repaint() # execute main function if not self.sampling(outPath, tableName): return self.outShape.clear() if self.addToMapCanvas.checkState() == Qt.Checked: uri = outPath layerName = outName if tableName: uri += "|layername=%s" % tableName layerName += ": %s" % tableName self.vlayer = QgsVectorLayer(uri, layerName, "ogr") if self.vlayer.isValid(): # Add the layer to the map, but first remove it if already present for l in QgsProject.instance().mapLayers().values(): if hasattr(l, 'source') and l.source() == self.vlayer.source(): QgsProject.instance().removeMapLayer(l) QgsProject.instance().addMapLayer(self.vlayer) self.statusLabel.setText(self.tr("OK. The new layer has been added to the map.")) else: self.statusLabel.setText(self.tr("Error loading the created layer")) QMessageBox.warning(self, self.tr("Point Sampling Tool"), self.tr("The new layer seems to be created, but is invalid.\nIt won't be loaded.")) def sampling(self, outPath, tableName): # main process # open sampling points layer pointLayer = self.sampItems[str(self.inSample.currentText())][0] pointProvider = pointLayer.dataProvider() allAttrs = pointProvider.attributeIndexes() sRs = pointLayer.crs() # create destination layer: first create list of selected fields fieldList = QgsFields() for i in range(len(self.fields)): if self.fields[i][0] == "point": #copying fields from source layer field = pointProvider.fields()[pointProvider.fieldNameIndex(self.sampItems[self.fields[i][1]][self.fields[i][2]][0])] field.setName(self.sampItems[self.fields[i][1]][self.fields[i][2]][1]) elif self.fields[i][0] == "poly": #copying fields from polygon layers polyLayer = self.polyItems[self.fields[i][1]][0] polyProvider = polyLayer.dataProvider() field = polyProvider.fields()[polyProvider.fieldNameIndex(self.polyItems[self.fields[i][1]][self.fields[i][2]][0])] field.setName(self.polyItems[self.fields[i][1]][self.fields[i][2]][1]) else: #creating fields for raster layers field = QgsField(self.rastItems[self.fields[i][1]][self.fields[i][2]][1], QVariant.Double, "real", 20, 5, "") ##### Better data type fit will be implemented in next versions fieldList.append(field) # create temporary memory layer (as it's currently impossible to set GPKG table name when writting features to QgsVectorFileWriter directly) memLayer = QgsVectorLayer("Point?crs=epsg:%d" % sRs.postgisSrid(), 'temp layer', 'memory') memLayer.startEditing() for field in fieldList: memLayer.addAttribute(field) memLayer.commitChanges() self.statusLabel.setText(self.tr("Writing data to the new layer...")) self.repaint() # process point after point... pointFeat = QgsFeature() np = 0 snp = pointProvider.featureCount() for pointFeat in pointProvider.getFeatures(): np += 1 if snp<100 or ( snp<5000 and ( np // 10.0 == np / 10.0 ) ) or ( np // 100.0 == np / 100.0 ): # display each or every 10th or every 100th point: self.statusLabel.setText(self.tr("Processing point %s of %s") % (np, snp)) self.repaint() # convert multipoint[0] to point pointGeom = pointFeat.geometry() if pointGeom.wkbType() == QgsWkbTypes.MultiPoint: pointPoint = pointGeom.asMultiPoint()[0] else: pointPoint = pointGeom.asPoint() outFeat = QgsFeature() outFeat.setGeometry(pointGeom) # ...and next loop inside: field after field bBox = QgsRectangle(pointPoint.x()-0.001,pointPoint.y()-0.001,pointPoint.x()+0.001,pointPoint.y()+0.001) # reuseable rectangle buffer around the point feature previousPolyLayer = None # reuse previous feature if it's still the same layer previousPolyFeat = None # reuse previous feature if it's still the same layer previousRastLayer = None # reuse previous raster multichannel sample if it's still the same layer previousRastSample = None # reuse previous raster multichannel sample if it's still the same layer attrs = [] for i in range(len(self.fields)): field = self.fields[i] if field[0] == "point": attr = pointFeat.attributes()[pointProvider.fieldNameIndex(self.sampItems[field[1]][field[2]][0])] attrs += [attr] elif field[0] == "poly": polyLayer = self.polyItems[field[1]][0] polyProvider = polyLayer.dataProvider() if polyLayer == previousPolyLayer: polyFeat = previousPolyFeat else: polyFeat = None pointGeom = QgsGeometry().fromPointXY(pointPoint) for iFeat in polyProvider.getFeatures(QgsFeatureRequest().setFilterRect(bBox)): if pointGeom.intersects(iFeat.geometry()): polyFeat = iFeat if polyFeat: attr = polyFeat.attributes()[polyProvider.fieldNameIndex(self.polyItems[field[1]][field[2]][0])] else: attr = None attrs += [attr] #only last one if more polygons overlaps!! This way we avoid attribute list overflow previousPolyLayer = polyLayer previousPolyFeat = polyFeat else: # field source is raster rastLayer = self.rastItems[field[1]][0] if rastLayer == previousRastLayer: rastSample = previousRastSample else: rastSample = rastLayer.dataProvider().identify(pointPoint, QgsRaster.IdentifyFormatValue).results() try: #bandName = self.rastItems[field[1]][field[2]][0] #depreciated bandNo = field[2] attr = float(rastSample[bandNo]) ##### !! float() - I HAVE TO IMPLEMENT RASTER TYPE HANDLING!!!! except: # point is out of raster extent attr = None attrs += [attr] previousRastLayer = rastLayer previousRastSample = rastSample outFeat.initAttributes(len(attrs)) outFeat.setAttributes(attrs) memLayer.dataProvider().addFeature(outFeat) # write the memlayer to the output file so=QgsVectorFileWriter.SaveVectorOptions() so.fileEncoding = 'UTF-8' if outPath.upper().endswith('SHP'): so.driverName = "ESRI Shapefile" elif outPath.upper().endswith('CSV'): so.driverName = "CSV" else: so.driverName = "GPKG" if tableName: so.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer so.layerName = tableName result, errMsg = QgsVectorFileWriter.writeAsVectorFormat(memLayer, outPath, so) if result: QMessageBox.critical(self, self.tr("Point sampling tool"), errMsg) return False else: del memLayer self.statusLabel.setText(self.tr("The new layer has been created.")) return True def testFieldsNames(self, fields): #tests uniquity of field names ok = True if len(fields) > 1: for field1 in fields: for field2 in fields: if field1[0] == "point": name1 = self.sampItems[field1[1]][field1[2]][1] elif field1[0] == "poly": name1 = self.polyItems[field1[1]][field1[2]][1] else: name1 = self.rastItems[field1[1]][field1[2]][1] if field2[0] == "point": name2 = self.sampItems[field2[1]][field2[2]][1] elif field2[0] == "poly": name2 = self.polyItems[field2[1]][field2[2]][1] else: name2 = self.rastItems[field2[1]][field2[2]][1] if (name1 == name2) and (field1 != field2): ok = False return ok
class Dialog(QDialog, Ui_Dialog): sampItems = { } # {name1 : [layer1, [field_src,field_dsn,Active?], [field_src,field_dsn,Active?], ...] , name2 : [layer2, ...] } polyItems = { } # {name1 : [layer1, [field_src,field_dsn,Active?], [field_src,field_dsn,Active?], ...] , name2 : [layer2, ...] } rastItems = { } # {name1 : [layer1, [band_name,field_dsn,Active?], [band_name,field_dsn,Active?], ...] , name2 : [layer2, ...] } fields = [ ] # [[type,layer,field],[type,layer,field],[type,layer,field]...] list of adresses of output fields def __init__(self, iface): QDialog.__init__(self) self.iface = iface self.setupUi(self) self.outButton.clicked.connect(self.outFile) self.inSample.currentIndexChanged.connect(self.updateFieldsList) self.inData.itemSelectionChanged.connect(self.updateFieldsTable) self.fieldsTable.cellChanged.connect(self.fieldNameChanged) self.addToMapCanvas.setCheckState(Qt.Checked) mapCanvas = self.iface.mapCanvas() # init dictionaries of items: self.sampItems = {} self.polyItems = {} self.rastItems = {} for i in range(mapCanvas.layerCount()): layer = mapCanvas.layer(i) if (layer.type() == layer.VectorLayer) and (layer.geometryType() == QgsWkbTypes.PointGeometry): # read point layers provider = layer.dataProvider() fields = provider.fields() theItem = [layer] for j in fields: theItem += [[str(j.name()), str(j.name()), False]] self.sampItems[str(layer.name())] = theItem self.inSample.addItem(layer.name()) elif (layer.type() == layer.VectorLayer) and (layer.geometryType() == QgsWkbTypes.PolygonGeometry): # read polygon layers provider = layer.dataProvider() fields = provider.fields() theItem = [layer] for j in fields: theItem += [[str(j.name()), str(j.name()), False]] self.polyItems[str(layer.name())] = theItem elif layer.type() == layer.RasterLayer: # read raster layers theItem = [layer] for j in range(layer.bandCount()): if layer.bandCount() == 1: name1 = layer.bandName(j + 1) name2 = layer.name()[:10] else: name1 = layer.bandName(j + 1) name2 = layer.name()[:8] + "_" + str(j + 1) theItem += [[name1, name2, False]] self.rastItems[str(layer.name())] = theItem self.updateFieldsList() def updateFieldsList(self): self.inData.clear() if not self.inSample.count(): return i = self.inSample.currentText() for j in range(1, len(self.sampItems[i])): #clear previously enabled fields (as they aren't selected in the widget) self.sampItems[i][j][2] = False self.inData.addItem(self.sampItems[i][0].name() + " : " + self.sampItems[i][j][0] + " (source point)") #NOT YET FINISHED - to be switched to tree rather # self.inData.addItem(str(self.sampItems[i][0].name()) + " (X coordinate)") # self.inData.addItem(str(self.sampItems[i][0].name()) + " (Y coordinate)") for i in self.polyItems: for j in range(1, len(self.polyItems[i])): self.inData.addItem( str(self.polyItems[i][0].name()) + " : " + str(self.polyItems[i][j][0]) + " (polygon)") for i in self.rastItems: for j in range(1, len(self.rastItems[i])): self.inData.addItem( str(self.rastItems[i][0].name()) + " : " + str(self.rastItems[i][j][0]) + " (raster)") self.updateFieldsTable() self.repaint() def updateFieldsTable(self): # called after selection changing # mark selected point items n = 0 i = self.inSample.currentText() for j in range(1, len(self.sampItems[i])): if self.inData.item(n) and self.inData.item(n).isSelected(): self.sampItems[i][j][2] = True else: self.sampItems[i][j][2] = False n += 1 # mark selected polygon items for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.inData.item(n) and self.inData.item(n).isSelected(): self.polyItems[i][j][2] = True else: self.polyItems[i][j][2] = False n += 1 # mark selected raster items (don't zero n; it's one list) for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.inData.item(n) and self.inData.item(n).isSelected(): self.rastItems[i][j][2] = True else: self.rastItems[i][j][2] = False n += 1 # fill the fieldsTable with point, then polygon and then raster items: self.fields = [] n = 0 self.fieldsTable.setRowCount(0) i = self.inSample.currentText() for j in range(1, len(self.sampItems[i])): if self.sampItems[i][j][2]: self.fields += [["point", i, j]] self.fieldsTable.setRowCount(n + 1) cell = QTableWidgetItem( str(self.sampItems[i][0].name()) + " : " + str(self.sampItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.fieldsTable.setItem(n, 0, cell) self.fieldsTable.setItem( n, 1, QTableWidgetItem(str(self.sampItems[i][j][1]))) n += 1 for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.polyItems[i][j][2]: self.fields += [["poly", i, j]] self.fieldsTable.setRowCount(n + 1) cell = QTableWidgetItem( str(self.polyItems[i][0].name()) + " : " + str(self.polyItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.fieldsTable.setItem(n, 0, cell) self.fieldsTable.setItem( n, 1, QTableWidgetItem(str(self.polyItems[i][j][1]))) n += 1 for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.rastItems[i][j][2]: self.fields += [["rast", i, j]] self.fieldsTable.setRowCount(n + 1) cell = QTableWidgetItem( str(self.rastItems[i][0].name()) + " : " + str(self.rastItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.fieldsTable.setItem(n, 0, cell) self.fieldsTable.setItem( n, 1, QTableWidgetItem(str(self.rastItems[i][j][1]))) n += 1 self.fieldsTable.resizeColumnsToContents() def fieldNameChanged( self, n): # called when any cell of the fieldsTable was modyfied # exit when false alarm if len(self.fields) == 0: return 0 if self.fieldsTable.rowCount() == 0: return 0 updatedItem = self.fieldsTable.item(n, 1) if updatedItem == None: return 0 # update items dictionaries updatedText = str(updatedItem.text()) if self.fields[n][0] == "point": self.sampItems[self.fields[n][1]][self.fields[n] [2]][1] = updatedText[:10] elif self.fields[n][0] == "poly": self.polyItems[self.fields[n][1]][self.fields[n] [2]][1] = updatedText[:10] else: self.rastItems[self.fields[n][1]][self.fields[n] [2]][1] = updatedText[:10] # cut to 10 characters if exceed if len(updatedText) > 10: self.updateFieldsTable() QMessageBox.information( self, "Point Sampling Tool", "Name length can't exceed 10 chars, so it has been truncated.") # This message box may to make some confusion, if user press OK while "too long name" is still under edition. # In this case, the message box pops up (correctly) and then the OK button becomes pressed, but the self.accept method is not called. # This pressed button with no action may to look like a hang up. # I've no idea either # how to pull this button up, or # how to forse execution the self.accept method, or # how to don't allow user to exceed 10 chars limit in QTableWidget cell. (THIS OPTION WOULD BE THE BEST!) self.fieldsTable.resizeColumnsToContents() def outFile(self): # by Carson Farmer 2008 # display file dialog for output file self.outShape.clear() outName, _ = QFileDialog().getSaveFileName( self, "Output file", ".", "GeoPackages(*.gpkg);;Comma separated values (*.csv);;Shapefiles (*.shp)", options=QFileDialog.DontConfirmOverwrite) outPath = QFileInfo(outName).absoluteFilePath() if not outPath.upper().endswith('.GPKG') and not outPath.upper( ).endswith('.CSV') and not outPath.upper().endswith('.SHP'): outPath += '.gpkg' if outName: self.outShape.clear() self.outShape.insert(outPath) def accept( self ): # Called when "OK" button pressed (based on the Carson Farmer's PointsInPoly Plugin, 2008) # check if all fields are filled up self.statusLabel.setText("Check input values, please!") nothingSelected = True for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.polyItems[i][j][2]: nothingSelected = False for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.rastItems[i][j][2]: nothingSelected = False if self.inSample.currentText() == "": self.tabWidget.setCurrentIndex(0) QMessageBox.information( self, "Point Sampling Tool", "Please select vector layer containing the sampling points") return if nothingSelected: self.tabWidget.setCurrentIndex(0) QMessageBox.information( self, "Point Sampling Tool", "Please select at least one polygon attribute or raster band") return if self.outShape.text() == "": self.tabWidget.setCurrentIndex(0) QMessageBox.information(self, "Point Sampling Tool", "Please specify output file name") return # check if destination field names are unique if not self.testFieldsNames(self.fields): self.updateFieldsTable() self.tabWidget.setCurrentIndex(1) QMessageBox.warning( self, "Point Sampling Tool", "At least two field names are the same!\nPlease type unique names." ) return # Check if there a CRS mismatch pointLayerSrid = list( self.sampItems.values())[0][0].crs().postgisSrid() msg = '''<html>All layers must have the same coordinate refere system. The <b>%s</b> layer seems to have different CRS id (<b>%d</b>) than the point layer (<b>%d</b>). If they are two different CRSes, you need to reproject one of the layers first, otherwise results will be wrong.<br/> However, if you are sure both CRSes are the same, and they are just improperly recognized, you can safely continue. Do you want to continue?</html>''' for i in self.polyItems: for j in range(1, len(self.polyItems[i])): if self.polyItems[i][j][2]: layerSrid = self.polyItems[i][0].crs().postgisSrid() if layerSrid != pointLayerSrid: if QMessageBox.question( self, "Point Sampling Tool: layer CRS mismatch!", msg % (i, layerSrid, pointLayerSrid), QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return for i in self.rastItems: for j in range(1, len(self.rastItems[i])): if self.rastItems[i][j][2]: layerSrid = self.rastItems[i][0].crs().postgisSrid() if layerSrid != pointLayerSrid: if QMessageBox.question( self, "Point Sampling Tool: layer CRS mismatch!", msg % (i, layerSrid, pointLayerSrid), QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return if True: # all tests passed! Let's go on self.statusLabel.setText("Processing the output file name...") self.repaint() outPath = self.outShape.text() outPath = outPath.replace("\\", "/") if not outPath.upper().endswith('.GPKG') and not outPath.upper( ).endswith('.CSV') and not outPath.upper().endswith('.SHP'): outPath += '.gpkg' outName = QFileInfo(outPath).fileName() tableName = None oldFile = QFile(outPath) if oldFile.exists(): if not outPath.upper().endswith('.GPKG'): if QMessageBox.question( self, "Point Sampling Tool", "File %s already exists. Do you want to overwrite?" % outName) == QMessageBox.No: # return to filling the input fields self.outShape.clear() self.statusLabel.setText( "Fill up the input fields, please.") self.repaint() return else: msg = """Please provide <b>table name</b> for your layer.<br/> <b>WARNING: </b>Database %s already exists. If you select a table existing in it, the table will be overwritten.""" % outName tableName, result = QInputDialog.getText( self, "Point Sampling Tool", msg, text=outName[:-5]) if not result: # return to filling the input fields self.outShape.clear() self.statusLabel.setText( "Fill up the input fields, please.") self.repaint() return self.statusLabel.setText("Processing...") self.repaint() # execute main function if not self.sampling(outPath, tableName): return self.outShape.clear() if self.addToMapCanvas.checkState() == Qt.Checked: uri = outPath layerName = outName if tableName: uri += "|layername=%s" % tableName layerName += ": %s" % tableName self.vlayer = QgsVectorLayer(uri, layerName, "ogr") if self.vlayer.isValid(): # Add the layer to the map, but first remove it if already present for l in QgsProject.instance().mapLayers().values(): if hasattr(l, 'source') and l.source( ) == self.vlayer.source(): QgsProject.instance().removeMapLayer(l) QgsProject.instance().addMapLayer(self.vlayer) self.statusLabel.setText( "OK. The new layer has been added to the map.") else: self.statusLabel.setText("Error loading the created layer") QMessageBox.warning( self, "Point Sampling Tool", "The new layer seems to be created, but is invalid.\nIt won't be loaded." ) def sampling(self, outPath, tableName): # main process # open sampling points layer pointLayer = self.sampItems[str(self.inSample.currentText())][0] pointProvider = pointLayer.dataProvider() allAttrs = pointProvider.attributeIndexes() sRs = pointLayer.crs() # create destination layer: first create list of selected fields fieldList = QgsFields() for i in range(len(self.fields)): if self.fields[i][0] == "point": #copying fields from source layer field = pointProvider.fields()[pointProvider.fieldNameIndex( self.sampItems[self.fields[i][1]][self.fields[i][2]][0])] field.setName( self.sampItems[self.fields[i][1]][self.fields[i][2]][1]) elif self.fields[i][ 0] == "poly": #copying fields from polygon layers polyLayer = self.polyItems[self.fields[i][1]][0] polyProvider = polyLayer.dataProvider() field = polyProvider.fields()[polyProvider.fieldNameIndex( self.polyItems[self.fields[i][1]][self.fields[i][2]][0])] field.setName( self.polyItems[self.fields[i][1]][self.fields[i][2]][1]) else: #creating fields for raster layers field = QgsField( self.rastItems[self.fields[i][1]][self.fields[i][2]][1], QVariant.Double, "real", 20, 5, "") ##### Better data type fit will be implemented in next versions fieldList.append(field) # create temporary memory layer (as it's currently impossible to set GPKG table name when writting features to QgsVectorFileWriter directly) memLayer = QgsVectorLayer("Point?crs=epsg:%d" % sRs.postgisSrid(), 'temp layer', 'memory') memLayer.startEditing() for field in fieldList: memLayer.addAttribute(field) memLayer.commitChanges() self.statusLabel.setText("Writing data to the new layer...") self.repaint() # process point after point... pointFeat = QgsFeature() np = 0 snp = pointProvider.featureCount() for pointFeat in pointProvider.getFeatures(): np += 1 if snp < 100 or (snp < 5000 and (np // 10.0 == np / 10.0)) or ( np // 100.0 == np / 100.0): # display each or every 10th or every 100th point: self.statusLabel.setText("Processing point %s of %s" % (np, snp)) self.repaint() # convert multipoint[0] to point pointGeom = pointFeat.geometry() if pointGeom.wkbType() == QgsWkbTypes.MultiPoint: pointPoint = pointGeom.asMultiPoint()[0] else: pointPoint = pointGeom.asPoint() outFeat = QgsFeature() outFeat.setGeometry(pointGeom) # ...and next loop inside: field after field bBox = QgsRectangle( pointPoint.x() - 0.001, pointPoint.y() - 0.001, pointPoint.x() + 0.001, pointPoint.y() + 0.001) # reuseable rectangle buffer around the point feature previousPolyLayer = None # reuse previous feature if it's still the same layer previousPolyFeat = None # reuse previous feature if it's still the same layer previousRastLayer = None # reuse previous raster multichannel sample if it's still the same layer previousRastSample = None # reuse previous raster multichannel sample if it's still the same layer attrs = [] for i in range(len(self.fields)): field = self.fields[i] if field[0] == "point": attr = pointFeat.attributes()[pointProvider.fieldNameIndex( self.sampItems[field[1]][field[2]][0])] attrs += [attr] elif field[0] == "poly": polyLayer = self.polyItems[field[1]][0] polyProvider = polyLayer.dataProvider() if polyLayer == previousPolyLayer: polyFeat = previousPolyFeat else: polyFeat = None pointGeom = QgsGeometry().fromPointXY(pointPoint) for iFeat in polyProvider.getFeatures( QgsFeatureRequest().setFilterRect(bBox)): if pointGeom.intersects(iFeat.geometry()): polyFeat = iFeat if polyFeat: attr = polyFeat.attributes()[ polyProvider.fieldNameIndex( self.polyItems[field[1]][field[2]][0])] else: attr = None attrs += [ attr ] #only last one if more polygons overlaps!! This way we avoid attribute list overflow previousPolyLayer = polyLayer previousPolyFeat = polyFeat else: # field source is raster rastLayer = self.rastItems[field[1]][0] if rastLayer == previousRastLayer: rastSample = previousRastSample else: rastSample = rastLayer.dataProvider().identify( pointPoint, QgsRaster.IdentifyFormatValue).results() try: #bandName = self.rastItems[field[1]][field[2]][0] #depreciated bandNo = field[2] attr = float( rastSample[bandNo] ) ##### !! float() - I HAVE TO IMPLEMENT RASTER TYPE HANDLING!!!! except: # point is out of raster extent attr = None attrs += [attr] previousRastLayer = rastLayer previousRastSample = rastSample outFeat.initAttributes(len(attrs)) outFeat.setAttributes(attrs) memLayer.dataProvider().addFeature(outFeat) # write the memlayer to the output file so = QgsVectorFileWriter.SaveVectorOptions() so.fileEncoding = 'UTF-8' if outPath.upper().endswith('SHP'): so.driverName = "ESRI Shapefile" elif outPath.upper().endswith('CSV'): so.driverName = "CSV" else: so.driverName = "GPKG" if tableName: so.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer so.layerName = tableName result, errMsg = QgsVectorFileWriter.writeAsVectorFormat( memLayer, outPath, so) if result: QMessageBox.critical(self, "Point sampling tool", errMsg) return False else: del memLayer self.statusLabel.setText("The new layer has been created.") return True def testFieldsNames(self, fields): #tests uniquity of field names ok = True if len(fields) > 1: for field1 in fields: for field2 in fields: if field1[0] == "point": name1 = self.sampItems[field1[1]][field1[2]][1] elif field1[0] == "poly": name1 = self.polyItems[field1[1]][field1[2]][1] else: name1 = self.rastItems[field1[1]][field1[2]][1] if field2[0] == "point": name2 = self.sampItems[field2[1]][field2[2]][1] elif field2[0] == "poly": name2 = self.polyItems[field2[1]][field2[2]][1] else: name2 = self.rastItems[field2[1]][field2[2]][1] if (name1 == name2) and (field1 != field2): ok = False return ok
class ExtractToPointsDialog(QDialog, UI_Dialog): targetItems = {} polygonItems = {} rasterItems = {} fields = [] def __init__(self, iface): QDialog.__init__(self) self.iface = iface self.setupUi(self) #connect self.BrowseButton.clicked.connect(self.browseFile) self.TargetLayer.currentIndexChanged.connect(self.updateFieldsList) self.AddToMap.setCheckState(Qt.Checked) self.SourceField.itemSelectionChanged.connect(self.updateFieldsTable) self.FieldsTable.cellChanged.connect(self.fieldNameChanged) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) mapCanvas = self.iface.mapCanvas() # initial dictionaries of items: self.targetItems = {} self.polygonItems = {} self.rasterItems = {} for i in range(mapCanvas.layerCount()): layer = mapCanvas.layer(i) if (layer.type() == layer.VectorLayer) and (layer.geometryType() == QgsWkbTypes.PointGeometry): # identify and get point layer provider = layer.dataProvider() fields = provider.fields() allItem = [layer] for j in fields: allItem += [[str(j.name()), str(j.name()), False]] self.targetItems[str(layer.name())] = allItem self.TargetLayer.addItem(layer.name()) elif (layer.type() == layer.VectorLayer) and (layer.geometryType() == QgsWkbTypes.PolygonGeometry): # identify and get polygon layer provider = layer.dataProvider() fields = provider.fields() allItem = [layer] for j in fields: allItem += [[str(j.name()), str(j.name()), False]] self.polygonItems[str(layer.name())] = allItem elif layer.type() == layer.RasterLayer: # identify and get raster layer allItem = [layer] for j in range(layer.bandCount()): if layer.bandCount() == 1: name1 = layer.bandName(j+1) name2 = layer.name()[:10] else: name1 = layer.bandName(j+1) name2 = layer.name()[:8] + "_" + str(j+1) allItem += [[name1, name2, False]] self.rasterItems[str(layer.name())] = allItem self.updateFieldsList() def updateFieldsList(self): self.SourceField.clear() if not self.TargetLayer.count(): return # i:layer j:fields i = self.TargetLayer.currentText() for j in range(1, len(self.targetItems[i])): #clear previously enabled fields (as they aren't selected in the widget) self.targetItems[i][j][2] = False self.SourceField.addItem(self.targetItems[i][0].name() + " : " + self.targetItems[i][j][0] + " (source layer field)") for i in self.polygonItems: for j in range(1, len(self.polygonItems[i])): self.SourceField.addItem(str(self.polygonItems[i][0].name()) + " : " + str(self.polygonItems[i][i][0]) + " (polygon)") for i in self.rasterItems: for j in range(1, len(self.rasterItems[i])): self.SourceField.addItem(str(self.rasterItems[i][0].name()) + " : " + str(self.rasterItems[i][j][0]) + " (raster)") self.updateFieldsTable() self.repaint() def updateFieldsTable(self): # after selection, update the table # mark selected point items n = 0 i = self.TargetLayer.currentText() for j in range(1, len(self.targetItems[i])): if self.SourceField.item(n) and self.SourceField.item(n).isSelected(): self.targetItems[i][j][2] = True else: self.targetItems[i][j][2] = False n += 1 # mark selected polygon items for i in self.polygonItems: for j in range(1, len(self.polygonItems[i])): if self.SourceField.item(n) and self.SourceField.item(n).isSelected(): self.polygonItems[i][j][2] = True else: self.polygonItems[i][j][2] = False n += 1 # mark selected raster items for i in self.rasterItems: for j in range(1, len(self.rasterItems[i])): if self.SourceField.item(n) and self.SourceField.item(n).isSelected(): self.rasterItems[i][j][2] = True else: self.rasterItems[i][j][2] = False n += 1 # fill FieldsTable with point, polygon and raster items self.fields = [] n = 0 self.FieldsTable.setRowCount(0) # point i = self.TargetLayer.currentText() for j in range(1, len(self.targetItems[i])): if self.targetItems[i][j][2]: self.fields += [["point",i,j]] self.FieldsTable.setRowCount(n+1) cell = QTableWidgetItem(str(self.targetItems[i][0].name()) + " : " + str(self.targetItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.FieldsTable.setItem(n, 0, cell) self.FieldsTable.setItem(n, 1, QTableWidgetItem(str(self.targetItems[i][j][1]))) n += 1 #polygon for i in self.polygonItems: for j in range(1, len(self.polygonItems[i])): if self.polygonItems[i][j][2]: self.fields += [["polygon",i,j]] self.FieldsTable.setRowCount(n+1) cell = QTableWidgetItem(str(self.polygonItems[i][0].name()) + " : " +str(self.polygonItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.FieldsTable.setItem(n, 0, cell) self.FieldsTable.setItem(n, 1, QTableWidgetItem(str(self.polygonItems[i][j][1]))) n += 1 #raster for i in self.rasterItems: for j in range(1, len(self.rasterItems[i])): if self.rasterItems[i][j][2]: self.fields += [["raster",i,j]] self.FieldsTable.setRowCount(n+1) cell = QTableWidgetItem(str(self.rasterItems[i][0].name()) + " : " +str(self.rasterItems[i][j][0])) cell.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) self.FieldsTable.setItem(n, 0, cell) self.FieldsTable.setItem(n, 1, QTableWidgetItem(str(self.rasterItems[i][j][1]))) n += 1 self.FieldsTable.resizeColumnsToContents() def fieldNameChanged(self, n): # when cell of the FieldsTable was modified # exit when false alarm if len(self.fields) == 0: return 0 if self.FieldsTable.rowCount() == 0: return 0 updatedItem = self.FieldsTable.item(n,1) if updatedItem == None: return 0 # update updatedText = str(updatedItem.text()) if self.fields[n][0] == "point": self.targetItems[self.fields[n][1]][self.fields[n][2]][1] = updatedText[:10] elif self.fields[n][0] == "polygon": self.polygonItems[self.fields[n][1]][self.fields[n][2]][1] = updatedText[:10] else: self.rasterItems[self.fields[n][1]][self.fields[n][2]][1] = updatedText[:10] # limit 10 characters if len(updatedText) > 10: self.updateFieldsTable() QMessageBox.information(self, self.tr("Extract Tool"), self.tr("Limit 10 Characters.")) self.FieldsTable.resizeColumnsToContents() def browseFile(self): # call dialog for output file self.OutLayer.clear() outName, _ = QFileDialog().getSaveFileName(self, self.tr("Output file"), '.', self.tr("Shapefiles (*.shp);;Comma separated values (*.csv);;GeoPackages(*.gpkg)"), options = QFileDialog.DontConfirmOverwrite) outPath = QFileInfo(outName).absoluteFilePath() if not outPath.upper().endswith('.SHP') and not outPath.upper().endswith('.CSV') and not outPath.upper().endswith('.GPKG'): outPath += '.gpkg' if outName: self.OutLayer.clear() self.OutLayer.insert(outPath) def accept(self): # "OK" button # check if all fields are filled up nothingSelected = True for i in self.polygonItems: for j in range(1, len(self.polygonItems[i])): if self.polygonItems[i][j][2]: nothingSelected = False for i in self.rasterItems: for j in range(1, len(self.rasterItems[i])): if self.rasterItems[i][j][2]: nothingSelected = False if self.TargetLayer.currentText() == "": QMessageBox.information(self, self.tr("Extract Tool"), self.tr("Please select target point layer")) return if nothingSelected: QMessageBox.information(self, self.tr("Extract Tool"), self.tr("Please select at least one field")) return if self.OutLayer.text() == "": QMessageBox.information(self, self.tr("Extract Tool"), self.tr("Please specify output file name")) return # check if target field names are unique if not self.testFieldNames(self.fields): self.updateFieldsTable() QMessageBox.warning(self, self.tr("Extract Tool"), self.tr("At least two field names are the same!\nPlease type unique names.")) return # check if there a CRS mismatch targetLayerSrid = list(self.targetItems.values())[0][0].crs().postgisSrid() msg = self.tr('''<html>All layers must have the same coordinate reference system. The <b>%s</b> layer seems to have different CRS id (<b>%d</b>) than the point layer (<b>%d</b>). If they are two different CRSes, you need to reproject one of the layers first, otherwise results will be wrong.<br/> However, if you are sure both CRSes are the same, and they are just improperly recognized, you can safely continue. Do you want to continue?</html>''') for i in self.polygonItems: for j in range(1, len(self.polygonItems[i])): if self.polygonItems[i][j][2]: layerSrid = self.polygonItems[i][0].crs().postgisSrid() if layerSrid != targetLayerSrid: if QMessageBox.question(self, self.tr("Extract Tool: layer CRS mismatch!"), msg % (i, layerSrid, targetLayerSrid), QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return for i in self.rasterItems: for j in range(1, len(self.rasterItems[i])): if self.rasterItems[i][j][2]: layerSrid = self.rasterItems[i][0].crs().postgisSrid() if layerSrid != targetLayerSrid: if QMessageBox.question(self, self.tr("Extract Tool: layer CRS mismatch!"), msg % (i, layerSrid, targetLayerSrid), QMessageBox.Yes | QMessageBox.No) != QMessageBox.Yes: return if True: # all tests passed, go on self.repaint() outPath = self.OutLayer.text() outPath = outPath.replace("\\","/") if not outPath.upper().endswith('.SHP') and not outPath.upper().endswith('.CSV') and not outPath.upper().endswith('.GPKG'): outPath += '.gpkg' outName = QFileInfo(outPath).fileName() tableName = None oldFile = QFile(outPath) if oldFile.exists(): if not outPath.upper().endswith('.GPKG'): if QMessageBox.question(self, self.tr("Extract Tool"), self.tr("File %s already exists. Do you want to overwrite?") % outName) == QMessageBox.No: self.OutLayer.clear() self.repaint() return else: msg = self.tr("""Please provide <b>table name</b> for your layer.<br/> <b>WARNING: </b>Database %s already exists. If you select a table existing in it, the table will be overwritten.""") % outName tableName, result = QInputDialog.getText(self, "Extract Tool", msg, text = outName[:-5]) if not result: self.OutLayer.clear() self.repaint() return self.repaint() # execute main function if not self.extract(outPath, tableName): return self.OutLayer.clear() if self.AddToMap.checkState() == Qt.Checked: uri = outPath layerName = outName if tableName: uri += "|layername=%s" % tableName layerName += ": %s" % tableName self.vlayer = QgsVectorLayer(uri, layerName, "ogr") if self.vlayer.isValid(): # Add layer to the map, but first remove it if already present for l in QgsProject.instance().mapLayers().values(): if hasattr(l, 'source') and l.source() == self.vlayer.source(): QgsProject.instance().removeMapLayer(l) QgsProject.instance().addMapLayer(self.vlayer) else: QMessageBox.warning(self, self.tr("Extract Tool"), self.tr("The new layer seems to be created, but is invalid.\nIt won't be loaded.")) def extract(self, outPath, tableName): # main process # open target point layer targetLayer = self.targetItems[str(self.TargetLayer.currentText())][0] targetprovider = targetLayer.dataProvider() allAttrs = targetprovider.attributeIndexes() sRs = targetLayer.crs() # create output layer: first create list of selected fields fieldList = QgsFields() for i in range(len(self.fields)): if self.fields[i][0] == "point": # copying fields from source layer field = targetprovider.fields()[targetprovider.fieldNameIndex(self.targetItems[self.fields[i][1]][self.fields[i][2]][0])] field.setName(self.targetItems[self.fields[i][1]][self.fields[i][2]][1]) elif self.fields[i][0] == "polygon": # copying fields from polygon layers polyLayer = self.polygonItems[self.fields[i][1]][0] polyProvider = polyLayer.dataProvider() field = polyProvider.fields()[polyProvider.fieldNameIndex(self.polygonItems[self.fields[i][1]][self.fields[i][2]][0])] field.setName(self.polygonItems[self.fields[i][1]][self.fields[i][2]][1]) else: # creating fields for raster layers field = QgsField(self.rasterItems[self.fields[i][1]][self.fields[i][2]][1], QVariant.Double, "real", 20, 5, "") fieldList.append(field) # create temporary memory layer memLayer = QgsVectorLayer("Point?crs=epsg:%d" % sRs.postgisSrid(), 'temp layer', 'memory') memLayer.startEditing() for field in fieldList: memLayer.addAttribute(field) memLayer.commitChanges() self.repaint() # process point after point pointFeat = QgsFeature() np = 0 snp = targetprovider.featureCount() for pointFeat in targetprovider.getFeatures(): np += 1 # convert multipoint[0] to point pointGeom = pointFeat.geometry() if pointGeom.wkbType() == QgsWkbTypes.MultiPoint: pointPoint = pointGeom.asMultiPoint()[0] else: pointPoint = pointGeom.asPoint() outFeat = QgsFeature() outFeat.setGeometry(pointGeom) # and next loop inside: field after field bBox = QgsRectangle(pointPoint.x()-0.001,pointPoint.y()-0.001,pointPoint.x()+0.001,pointPoint.y()+0.001) # reusable rectangle buffer around the point feature previousPolyLayer = None # reuse previous feature if it's still the same layer previousPolyFeat = None # reuse previous feature if it's still the same layer previousRastLayer = None # reuse previous raster multichannel sample if it's still the same layer previousRastSample = None # reuse previous raster multichannel sample if it's still the same layer attrs = [] for i in range(len(self.fields)): field = self.fields[i] if field[0] == "point": attr = pointFeat.attributes()[targetprovider.fieldNameIndex(self.targetItems[field[1]][field[2]][0])] attrs += [attr] elif field[0] == "polygon": polyLayer = self.polygonItems[field[1]][0] polyProvider = polyLayer.dataProvider() if polyLayer == previousPolyLayer: polyFeat = previousPolyFeat else: polyFeat = None pointGeom = QgsGeometry().fromPointXY(pointPoint) for iFeat in polyProvider.getFeatures(QgsFeatureRequest().setFilterRect(bBox)): if pointGeom.intersects(iFeat.geometry()): polyFeat = iFeat if polyFeat: attr = polyFeat.attributes()[polyProvider.fieldNameIndex(self.polygonItems[field[1]][field[2]][0])] else: attr = None attrs += [attr] #only last one if more polygons overlaps previousPolyLayer = polyLayer previousPolyFeat = polyFeat else: # raster rastLayer = self.rasterItems[field[1]][0] if rastLayer == previousRastLayer: rastSample = previousRastSample else: rastSample = rastLayer.dataProvider().identify(pointPoint, QgsRaster.IdentifyFormatValue).results() try: bandNo = field[2] attr = float(rastSample[bandNo]) except: # point is out of raster extent attr = None attrs += [attr] previousRastLayer = rastLayer previousRastSample = rastSample outFeat.initAttributes(len(attrs)) outFeat.setAttributes(attrs) memLayer.dataProvider().addFeature(outFeat) # write memlayer to the output file so = QgsVectorFileWriter.SaveVectorOptions() so.fileEncoding = 'UTF-8' if outPath.upper().endswith('SHP'): so.driverName = "ESRI Shapefile" elif outPath.upper().endswith('CSV'): so.driverName = "CSV" else: so.driverName = "GPKG" if tableName: so.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer so.layerName = tableName result, errMsg = QgsVectorFileWriter.writeAsVectorFormat(memLayer, outPath, so) if result: QMessageBox.critical(self, self.tr("Extract tool"), errMsg) return False else: del memLayer return True def testFieldNames(self, fields): #tests uniqueness of field names ok = True if len(fields) > 1: for field1 in fields: for field2 in fields: if field1[0] == "point": name1 = self.targetItems[field1[1]][field1[2]][1] elif field1[0] == "polygon": name1 = self.polygonItems[field1[1]][field1[2]][1] else: name1 = self.rasterItems[field1[1]][field1[2]][1] if field2[0] == "point": name2 = self.targetItems[field2[1]][field2[2]][1] elif field2[0] == "polygon": name2 = self.polygonItems[field2[1]][field2[2]][1] else: name2 = self.rasterItems[field2[1]][field2[2]][1] if (name1 == name2) and (field1 != field2): ok = False return ok
def calc(progress_bars, receiver_layer, source_pts_layer, source_roads_layer, settings, level_field_index, obstacles_layer, rays_writer, diff_rays_writer): research_ray = int(settings['research_ray']) temperature = int(settings['temperature']) humidity = int(settings['humidity']) time = datetime.now() ## create diffraction points if obstacles_layer is not None: bar = progress_bars['create_dif']['bar'] diffraction_points_layer_path = os.path.abspath( os.path.join(temp_dir + os.sep + "diffraction_pts.shp")) on_CreateDiffractionPoints.run(bar, obstacles_layer.source(), diffraction_points_layer_path) diffraction_layer_name = 'diff' diffraction_layer = QgsVectorLayer(diffraction_points_layer_path, diffraction_layer_name, "ogr") progress_bars['create_dif']['label'].setText( 'Done in ' + duration(time, datetime.now())) # print 'crea diffraction points ',datetime.now() - time time = datetime.now() # Create emission layer that will contain all the emission pts from source_pts and source_roads emission_pts_layer_path = os.path.abspath( os.path.join(temp_dir + os.sep + "emission_pts.shp")) # emission_pts_fields = [QgsField("type", QVariant.String), # QgsField("id_source", QVariant.Int), # QgsField("segment", QVariant.String),] emission_pts_fields = QgsFields() emission_pts_fields.append(QgsField("type", QVariant.String)) emission_pts_fields.append(QgsField("id_source", QVariant.Int)) emission_pts_fields.append(QgsField("segment", QVariant.String)) emission_pts_writer = QgsVectorFileWriter(emission_pts_layer_path, "System", emission_pts_fields, QgsWkbTypes.Point, receiver_layer.crs(), "ESRI Shapefile") bar = progress_bars['prepare_emi']['bar'] bar.setValue(1) source_feat_all_dict = {} # pts source layer to emission pts layer if source_pts_layer is not None: # get emission levels and add feat to emission pts layer source_pts_levels_dict = {} source_pts_feat_all = source_pts_layer.dataProvider().getFeatures() for source_feat in source_pts_feat_all: # get emission values levels = get_levels(settings, source_pts_layer, source_feat) source_pts_levels_dict[source_feat.id()] = levels # add feat to emission pts layer source_feat.setAttributes(['pt', source_feat.id(), None]) emission_pts_writer.addFeature(source_feat) # roads source layer to emission pts layer if source_roads_layer is not None: ## create emission points from roads source emission_pts_roads_layer_path = os.path.abspath( os.path.join(temp_dir + os.sep + "emission_pts_roads.shp")) on_CreateEmissionPoints.run(source_roads_layer.source(), receiver_layer.source(), emission_pts_roads_layer_path, research_ray) emission_pts_roads_layer = QgsVectorLayer( emission_pts_roads_layer_path, 'emission_pts_roads', "ogr") # get levels from the road source source_roads_levels_dict = {} source_roads_feat_all = source_roads_layer.dataProvider().getFeatures() for source_feat in source_roads_feat_all: levels = get_levels(settings, source_roads_layer, source_feat) source_roads_levels_dict[source_feat.id()] = levels # add roads pts to emission pts layer source_pts_roads_feat_all = emission_pts_roads_layer.dataProvider( ).getFeatures() for source_feat in source_pts_roads_feat_all: id_source = source_feat['id_source'] segment_source = source_feat[ 'd_rTOe'] # Pierluigi -- corretto perchè chiamava segment # add feat to emission pts layer source_feat.setAttributes(['road', id_source, segment_source]) emission_pts_writer.addFeature(source_feat) del emission_pts_writer # Create dict with all the data source_feat_all_dict = {} source_layer = QgsVectorLayer(emission_pts_layer_path, 'emission pts', "ogr") source_feat_all = source_layer.dataProvider().getFeatures() source_feat_total = source_layer.dataProvider().featureCount() source_feat_number = 0 for source_feat in source_feat_all: source_feat_number = source_feat_number + 1 barValue = source_feat_number / float(source_feat_total) * 100 bar.setValue(barValue) type_source = source_feat['type'] id_source = source_feat['id_source'] segment = source_feat['segment'] if type_source == 'pt': levels = source_pts_levels_dict[id_source] if type_source == 'road': levels = source_roads_levels_dict[id_source] value = {} value['type'] = type_source value['feat'] = source_feat value['global'] = levels['global'] value['bands'] = levels['bands'] value['segment'] = segment source_feat_all_dict[source_feat.id()] = value ## get data # receiver layer receiver_feat_all = receiver_layer.dataProvider().getFeatures() receiver_feat_total = receiver_layer.dataProvider().featureCount() receiver_feat_number = 0 # obstacles layer if obstacles_layer is not None: obstacles_feat_all = obstacles_layer.dataProvider().getFeatures() obstacles_feat_all_dict = {} for obstacles_feat in obstacles_feat_all: obstacles_feat_all_dict[obstacles_feat.id()] = obstacles_feat # diffraction layer diff_feat_all = diffraction_layer.dataProvider().getFeatures() diff_feat_all_dict = {} for diff_feat in diff_feat_all: diff_feat_all_dict[diff_feat.id()] = diff_feat progress_bars['prepare_emi']['label'].setText( 'Done in ' + duration(time, datetime.now())) # fix_print_with_import print('get acoustic data', datetime.now() - time) time = datetime.now() if obstacles_layer is None: bar = progress_bars['recTOsou']['bar'] recTOsource_dict = on_RaysSearch.run(bar, receiver_layer.source(), source_layer.source(), None, research_ray) progress_bars['recTOsou']['label'].setText( 'Done in ' + duration(time, datetime.now())) # fix_print_with_import print('find connections receivers sources ', datetime.now() - time) time = datetime.now() diffTOsource_dict = {} recTOdiff_dict = {} else: ### recTOsou bar = progress_bars['recTOsou']['bar'] recTOsource_dict = on_RaysSearch.run(bar, receiver_layer.source(), source_layer.source(), obstacles_layer.source(), research_ray) progress_bars['recTOsou']['label'].setText( 'Done in ' + duration(time, datetime.now())) # fix_print_with_import print('find connections receceivers sources ', datetime.now() - time) time = datetime.now() ### difTOsou bar = progress_bars['difTOsou']['bar'] diffTOsource_dict = on_RaysSearch.run(bar, diffraction_layer.source(), source_layer.source(), obstacles_layer.source(), research_ray) progress_bars['difTOsou']['label'].setText( 'Done in ' + duration(time, datetime.now())) # fix_print_with_import print('find connections diffraction points sources', datetime.now() - time) time = datetime.now() ### recTOdif bar = progress_bars['recTOdif']['bar'] # recTOdiff_dict = on_RaysSearch.run_selection_distance(bar,receiver_layer.source(),diffraction_layer.source(),obstacles_layer.source(),research_ray,diffTOsource_dict,source_layer.source()) recTOdiff_dict = on_RaysSearch.run_selection( bar, receiver_layer.source(), diffraction_layer.source(), obstacles_layer.source(), research_ray, diffTOsource_dict) progress_bars['recTOdif']['label'].setText( 'Done in ' + duration(time, datetime.now())) # fix_print_with_import print('find connectino receivers diffraction points', datetime.now() - time) time = datetime.now() ray_id = 0 diff_ray_id = 0 receiver_feat_all_new_fields = {} bar = progress_bars['calculate']['bar'] for receiver_feat in receiver_feat_all: receiver_feat_number = receiver_feat_number + 1 barValue = receiver_feat_number / float(receiver_feat_total) * 100 bar.setValue(barValue) receiver_feat_new_fields = {} # initializes the receiver point lin level receiver_point_lin_level = {} receiver_point_lin_level['gen'] = 0 receiver_point_lin_level['day'] = 0 receiver_point_lin_level['eve'] = 0 receiver_point_lin_level['nig'] = 0 if receiver_feat.id() in recTOsource_dict: source_ids = recTOsource_dict[receiver_feat.id()] for source_id in source_ids: source_feat_value = source_feat_all_dict[source_id] source_feat = source_feat_value['feat'] ray_geometry = QgsGeometry.fromPolylineXY([ receiver_feat.geometry().asPoint(), source_feat.geometry().asPoint() ]) d_recTOsource = compute_distance( receiver_feat.geometry().asPoint(), source_feat.geometry().asPoint()) # length with receiver points height fixed to 4 m d_recTOsource_4m = sqrt(d_recTOsource**2 + 16) feat_type = source_feat_value['type'] level_emi = source_feat_value['global'] level_emi_bands = source_feat_value['bands'] segment = source_feat_value['segment'] level_dir = {} level_atm_bands = {} geo_attenuation = on_Acoustics.GeometricalAttenuation( 'spherical', d_recTOsource_4m) for key in list(level_emi.keys()): if level_emi[key] > 0: level_atm_bands[ key] = on_Acoustics.AtmosphericAbsorption( d_recTOsource, temperature, humidity, level_emi_bands[key]).level() level_dir[key] = on_Acoustics.OctaveBandsToGlobal( level_atm_bands[key]) - geo_attenuation # correction for the segment lenght if feat_type == 'road': if (settings['implementation_roads'] == 'POWER_R' or settings['implementation_roads'] == 'NMPB'): level_dir[key] = level_dir[ key] + 20 + 10 * log10(float(segment)) + 3 if settings['implementation_roads'] == 'CNOSSOS': level_dir[key] = level_dir[key] + 10 * log10( float(segment)) + 3 receiver_point_lin_level[ key] = receiver_point_lin_level[key] + 10**( level_dir[key] / float(10)) else: # level_dir[key] = -1 if rays_writer is not None: ray = QgsFeature() ray.setGeometry(ray_geometry) attributes = [ ray_id, receiver_feat.id(), source_feat.id(), d_recTOsource, d_recTOsource_4m ] if settings['period_pts_gen'] == "True" or settings[ 'period_roads_gen'] == "True": if 'gen' in level_emi: attributes.append(level_emi['gen']) attributes.append(level_dir['gen']) else: attributes.append(None) attributes.append(None) if settings['period_pts_day'] == "True" or settings[ 'period_roads_day'] == "True": if 'day' in level_emi: attributes.append(level_emi['day']) attributes.append(level_dir['day']) else: attributes.append(None) attributes.append(None) if settings['period_pts_eve'] == "True" or settings[ 'period_roads_eve'] == "True": if 'eve' in level_emi: attributes.append(level_emi['eve']) attributes.append(level_dir['eve']) else: attributes.append(None) attributes.append(None) if settings['period_pts_nig'] == "True" or settings[ 'period_roads_nig'] == "True": if 'nig' in level_emi: attributes.append(level_emi['nig']) attributes.append(level_dir['nig']) else: attributes.append(None) attributes.append(None) ray.setAttributes(attributes) rays_writer.addFeature(ray) ray_id = ray_id + 1 if receiver_feat.id() in recTOdiff_dict: diff_ids = recTOdiff_dict[receiver_feat.id()] for diff_id in diff_ids: diff_feat = diff_feat_all_dict[diff_id] if diff_feat.id() in diffTOsource_dict: source_ids = diffTOsource_dict[diff_feat.id()] for source_id in source_ids: source_feat_value = source_feat_all_dict[source_id] source_feat = source_feat_value['feat'] if receiver_feat.id() in recTOsource_dict: source_ids = recTOsource_dict[receiver_feat.id()] if source_feat.id() in source_ids: shadow = 0 else: shadow = 1 else: shadow = 1 if shadow == 1: ray_geometry = QgsGeometry.fromPolylineXY([ receiver_feat.geometry().asPoint(), diff_feat.geometry().asPoint(), source_feat.geometry().asPoint() ]) d_recTOdiff = compute_distance( receiver_feat.geometry().asPoint(), diff_feat.geometry().asPoint()) d_diffTOsource = compute_distance( diff_feat.geometry().asPoint(), source_feat.geometry().asPoint()) d_recTOsource = compute_distance( receiver_feat.geometry().asPoint(), source_feat.geometry().asPoint()) d_recPLUSsource = d_recTOdiff + d_diffTOsource if d_recPLUSsource <= research_ray: feat_type = source_feat_value['type'] level_emi = source_feat_value['global'] level_emi_bands = source_feat_value['bands'] segment = source_feat_value['segment'] level_dif = {} level_dif_bands = {} level_atm_bands = {} for key in list(level_emi_bands.keys()): if level_emi[key] > 0: level_dif_bands[ key] = on_Acoustics.Diffraction( 'CNOSSOS', level_emi_bands[key], d_diffTOsource, d_recTOsource, d_recTOdiff).level() level_atm_bands[ key] = on_Acoustics.AtmosphericAbsorption( d_recPLUSsource, temperature, humidity, level_emi_bands[key] ).attenuation() level_dif_bands[ key] = on_Acoustics.DiffBands( level_dif_bands[key], level_atm_bands[key]) level_dif[ key] = on_Acoustics.OctaveBandsToGlobal( level_dif_bands[key]) # correction for the segment lenght if feat_type == 'road': if (settings['implementation_roads'] == 'POWER_R' or settings[ 'implementation_roads'] == 'NMPB'): level_dif[key] = level_dif[ key] + 20 + 10 * log10( float(segment)) + 3 if settings[ 'implementation_roads'] == 'CNOSSOS': level_dif[key] = level_dif[ key] + 10 * log10( float(segment)) + 3 receiver_point_lin_level[ key] = receiver_point_lin_level[ key] + 10**(level_dif[key] / float(10)) else: level_dif[key] = -1 if diff_rays_writer is not None: ray = QgsFeature() ray.setGeometry(ray_geometry) attributes = [ diff_ray_id, receiver_feat.id(), diff_feat.id(), source_feat.id(), d_recTOdiff, d_diffTOsource, d_recTOsource ] if settings[ 'period_pts_gen'] == "True" or settings[ 'period_roads_gen'] == "True": if 'gen' in level_emi: attributes.append(level_emi['gen']) attributes.append(level_dif['gen']) else: attributes.append(None) attributes.append(None) if settings[ 'period_pts_day'] == "True" or settings[ 'period_roads_day'] == "True": if 'day' in level_emi: attributes.append(level_emi['day']) attributes.append(level_dif['day']) else: attributes.append(None) attributes.append(None) if settings[ 'period_pts_eve'] == "True" or settings[ 'period_roads_eve'] == "True": if 'eve' in level_emi: attributes.append(level_emi['eve']) attributes.append(level_dif['eve']) else: attributes.append(None) attributes.append(None) if settings[ 'period_pts_nig'] == "True" or settings[ 'period_roads_nig'] == "True": if 'nig' in level_emi: attributes.append(level_emi['nig']) attributes.append(level_dif['nig']) else: attributes.append(None) attributes.append(None) ray.setAttributes(attributes) diff_rays_writer.addFeature(ray) diff_ray_id = diff_ray_id + 1 if settings['period_pts_gen'] == "True" or settings[ 'period_roads_gen'] == "True": if receiver_point_lin_level['gen'] > 0: Lgen = 10 * log10(receiver_point_lin_level['gen']) if Lgen < 0: Lgen = 0 receiver_feat_new_fields[level_field_index['gen']] = Lgen else: receiver_feat_new_fields[level_field_index['gen']] = -99 Lday = 0 Leve = 0 Lnig = 0 #addec contron on final data if negative set to zero if settings['period_pts_day'] == "True" or settings[ 'period_roads_day'] == "True": if receiver_point_lin_level['day'] > 0: Lday = 10 * log10(receiver_point_lin_level['day']) if Lday < 0: Lday = 0 receiver_feat_new_fields[level_field_index['day']] = Lday else: receiver_feat_new_fields[level_field_index['day']] = -99 if settings['period_pts_eve'] == "True" or settings[ 'period_roads_eve'] == "True": if receiver_point_lin_level['eve'] > 0: Leve = 10 * log10(receiver_point_lin_level['eve']) if Leve < 0: Leve = 0 receiver_feat_new_fields[level_field_index['eve']] = Leve else: receiver_feat_new_fields[level_field_index['eve']] = -99 if settings['period_pts_nig'] == "True" or settings[ 'period_roads_nig'] == "True": if receiver_point_lin_level['nig'] > 0: Lnig = 10 * log10(receiver_point_lin_level['nig']) if Lnig < 0: Lnig = 0 receiver_feat_new_fields[level_field_index['nig']] = Lnig else: receiver_feat_new_fields[level_field_index['nig']] = -99 if settings['period_den'] == "True": receiver_feat_new_fields[ level_field_index['den']] = on_Acoustics.Lden( Lday, Leve, Lnig, int(settings['day_hours']), int(settings['eve_hours']), int(settings['nig_hours']), int(settings['day_penalty']), int(settings['eve_penalty']), int(settings['nig_penalty'])) receiver_feat_all_new_fields[ receiver_feat.id()] = receiver_feat_new_fields progress_bars['calculate']['label'].setText('Done in ' + duration(time, datetime.now())) # fix_print_with_import print('calculate levels and, if selected, draw rays', datetime.now() - time) time = datetime.now() return receiver_feat_all_new_fields