.salvami e condividimi: backup di un progetto QGIS su Dropbox

Al giorno d’oggi esistono svariati strumenti che ci consentono di avere uno spazio di archiviazione remoto. Credo possiamo dire che non abbiamo più limiti di spazio per archiviare i nostri documenti le nostre foto ed ogni altra cosa che può essere dematerializzata in un file system. Tanti sono i provider che offrono gratuitamente Gigabyte di spazio per i nostri files. Tra questi, uno dei più utilizzati è sicuramente Dropbox che consente di memorizzare foto, video e tantissimi altre tipologie di files, ormai così diffuso ed utile al punto da non poterne più fare a meno.

Partendo da questa domanda Why can’t I share project files with another QGIS user?, ho pensato di creare e condividere uno script python, che a me è tornato utile, che consente di eseguire un backup di un progetto QGIS in una cartella remota di Dropbox in modo da poter essere velocemente condiviso.

Schermata 2016-03-20 alle 15.28.24

Bene, procediamo! Per prima cosa è necessario fare l’accesso a Dropbox e poi creare una nuova “app folder”, così si chiama. Per crearla è sufficiente andare alla pagina per sviluppatori ed in alto a destra, appena sotto il nome utente, selezionare “Create App”. Dopo aver creato l’app folder è possibile visualizzare ed impostare alcune opzioni tra cui la possibilità di generare un Token. Il Token è necessario per accedere in lettura e scrittura alla cartella attraverso l’uso delle API di Dropbox.

Una volta che il Token è stato generato deve essere copiato nella parte di codice presente nello script, sostituendolo al testo ‘your_token_here‘. Lo script di seguito illustrato va eseguito dalla Console Python di QGIS, non funziona come script standalone per il momento.

Schermata 2016-03-16 alle 22.41.59
Console python di QGIS

Non sarà generato nessun output in locale (tranne qualche file temporaneo), durante l’esecuzione sarà creata una directory (data) contenente tutti i layer ed un file di progetto, il tutto all’interno di una cartella remota di Dropbox, come mostrato nella seguente figura.

Schermata 2016-03-17 alle 21.47.55
Cartella su Dropbox

Dopo essere stato eseguito, lo script stamperà nella console il link alla cartella Dropbox del progetto, pronto per essere condiviso con chiunque.

Adesso diamo un’occhiata al codice. Tralasciando per il momento la parte relativa alla chiamata per il client dropbox, necessario per trasferire i file nella cartella creata precedentemente, lo script è formato da quattro funzioni che cercherò di riassumere di seguito.

TOKEN = 'your_token_here'

SAVED_PRJ_FILE = QgsProject.instance().fileName()
PRJ_FILENAME = QFileInfo(SAVED_PRJ_FILE).fileName()
PRJ_FILEPATH = os.path.join(tempfile.gettempdir(), PRJ_FILENAME)
SUFFIX_PRJNAME = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
MAIN_FOLDER = '-'.join([PRJ_FILENAME[:-4], SUFFIX_PRJNAME])

client = dropbox.client.DropboxClient(TOKEN)
# crea due liste una per i dati vettoriali e l'altra per i dati raster
vlayers, rlayers = getPrjLayers()
# crea una lista di path, i percorsi alla sorgente dati dei layers
paths = getPathLayers(vlayers, rlayers)
# trasferisce tutti i layers in Dropbox
copySourceLayersToDropbox(paths)
# aggiorna il file di progetto *.qgs
updateProjectFile()
# stampa il link per la condivisione del progetto
# per esempio: https://db.tt/GkU39Son
shared_link = client.share(MAIN_FOLDER, short_url=True)
print shared_link['url']
  • vlayers, rlayers = getPrjLayers()

La prima funzione serve a recuperare tutti i layers presenti nella legenda del progetto QGIS (*.qgs) caricato. Questo avviene usando direttamente le API di QGIS, la funzione ritorna due liste di layers, una per i dati vettoriali e l’altra per i dati raster.

def getPrjLayers():
    vlayers = []
    rlayers = []

    prj = QgsProject.instance()
    root = prj.layerTreeRoot()
    layerNodes = root.findLayers()

    for node in layerNodes:
        if node.layer().type() == QgsVectorLayer.VectorLayer:
            if node.layer().storageType() == 'ESRI Shapefile' or node.layer().providerType() == 'postgres':
                vlayers.append(node.layer())
        if node.layer().type() == QgsVectorLayer.RasterLayer:
            rlayers.append(node.layer())

    return vlayers, rlayers
  • paths = getPathLayers(vlayers, rlayers)

La seconda funzione consente di creare una lista con il percorso dei layers salvati nel file system o in caso di connessioni a database il layer sarà esportato in formato ESRI Shapefile all’interno della cartella temporanea di sistema.

def getPathLayers(vlayers, rlayers):
    paths = []
    for l in vlayers:
        if l.providerType() == 'postgres':
            crs = l.crs()
            encoding = l.dataProvider().encoding()
            out_file = '{0}/{1}.shp'.format(tempfile.gettempdir(), l.name())
            error = QgsVectorFileWriter.writeAsVectorFormat(l, out_file, encoding, crs)
            if error != QgsVectorFileWriter.NoError:
                print 'Cannot copy layer {0}'.format(l.name())
                return
            paths.append(out_file)
        else:
            paths.append(l.source())

    for r in rlayers:
        paths.append(r.source())

    return paths
  • copySourceLayersToDropbox(paths)

Dopo l’esecuzione delle prime due funzione tutto è pronto per essere trasferito e salvato su Dropbox. Il trasferimento avviene attraverso la funzione put_file() disponibile nel  modulo python di Dropbox.

def copySourceLayersToDropbox(paths):
    for path in paths:
        dir = QFileInfo(path).path()
        basename = QFileInfo(path).baseName()

        files = glob.glob(os.path.join(dir, basename) + '.*')

        for f in files:
            dropbox_file_name = os.path.join(MAIN_FOLDER, 'data', QFileInfo(f).fileName())
            with open(f, 'rb') as fd:
                client.put_file(dropbox_file_name, fd)
  • updateProjectFile()

Infine è necessario aggiornare il file di progetto il *.qgs. Trattandosi di un file XML e dovendo solo modificare il percorso della sorgente dati (datasource) ho utilizzato il modulo xml di python per effettuare le modifiche sul file di progetto. La funzione updateProjectFile() fa esattamente ciò che ho appena descritto.

def updateProjectFile():
    with open(PRJ_FILEPATH, 'wb') as f:
        f.write(open(SAVED_PRJ_FILE, 'r').read())

    tree = ET.parse(PRJ_FILEPATH)
    root = tree.getroot()

    vlayers, rlayers = getPrjLayers()
    layers = list(set(vlayers).union(rlayers))
    for layer in layers:
        for maplayer in root.iter('maplayer'):
            if layer.id() == maplayer.find('id').text:
                maplayer.find('datasource').text = u'./data/' + layer.name() + '.shp'
                maplayer.find('provider').text = u'ogr'

    tree.write(PRJ_FILEPATH)
    with open(PRJ_FILEPATH, 'rb') as fd:
        client.put_file(os.path.join(MAIN_FOLDER, PRJ_FILENAME), fd)

Allego l’intero script da copiare ed incollare direttamente nella console python ed eseguire. Lo script ha una dipendenza al modulo dropbox che deve essere installato sul sistema per il corretto funzionamento dello stesso.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s