-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
dynamic_layers_engine.py
415 lines (338 loc) · 13.8 KB
/
dynamic_layers_engine.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# -*- coding: utf-8 -*-
"""
/***************************************************************************
DynamicLayersEngine
A QGIS plugin
This plugin helps to change the datasource of chosen layers dynamically by searching and replacing user defined variables.
-------------------
begin : 2015-07-21
git sha : $Format:%H$
copyright : (C) 2015 by Michaël Douchin - 3liz
email : mdouchin@3liz.com
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
"""
from PyQt4.QtCore import Qt, QSettings, QTranslator, qVersion, QCoreApplication
from PyQt4.QtGui import QAction, QIcon,QTableWidgetItem, QTextCursor, qApp
from PyQt4.QtXml import QDomDocument,QDomElement
from qgis.core import QgsMapLayerRegistry, QgsMapLayer, QgsProject, QgsExpression, QgsFeatureRequest, QgsMessageLog
try:
from qgis.utils import iface
except:
iface = None
pass
import os.path
import sys
import re
class dynamicLayersTools():
def searchAndReplaceStringByDictionary(self, string='', dictionary={}):
'''
Get the string,
Replace variable such as {$VAR} with passed data,
And returns the updated string
'''
# Check everything is ok
if not string:
return ''
if not dictionary or not isinstance( dictionary, dict ):
return string
# Create new string from original string by replacing via dic
for k, v in dictionary.items():
# Replace search string by value
if v:
r = re.compile('\{\$%s\}' % k, re.MULTILINE)
string = r.sub( '%s' % v, string )
return string
class layerDataSourceModifier():
# Content of the dynamic datasource
dynamicDatasourceContent = None
# Datasource can be changed from dynamicDatasourceContent or not
dynamicDatasourceActive = False
def __init__(
self,
layer
):
'''
Initialize class instance
'''
if not layer:
return
self.layer = layer
self.dynamicDatasourceActive = ( layer.customProperty('dynamicDatasourceActive') == 'True' )
self.dynamicDatasourceContent = layer.customProperty( 'dynamicDatasourceContent')
def setNewSourceUriFromDict( self, searchAndReplaceDictionary={} ):
'''
Get the dynamic datasource template,
Replace variable with passed data,
And set the layer datasource from this content if possible
'''
# Get template uri
uriTemplate = self.dynamicDatasourceContent
# Set the new uri
t = dynamicLayersTools()
newUri = t.searchAndReplaceStringByDictionary( uriTemplate, searchAndReplaceDictionary )
# Set the layer datasource
self.setDataSource( newUri )
# Set other properties
self.setDynamicLayerProperties( searchAndReplaceDictionary )
def setDataSource( self, newSourceUri):
'''
Method to apply a new datasource to a vector Layer
'''
layer = self.layer
newDS, newUri = self.splitSource(newSourceUri)
newDatasourceType = newDS or layer.dataProvider().name()
# read layer definition
XMLDocument = QDomDocument("style")
XMLMapLayers = QDomElement()
XMLMapLayers = XMLDocument.createElement("maplayers")
XMLMapLayer = QDomElement()
XMLMapLayer = XMLDocument.createElement("maplayer")
layer.writeLayerXML(XMLMapLayer,XMLDocument)
# apply layer definition
XMLMapLayer.firstChildElement("datasource").firstChild().setNodeValue(newUri)
XMLMapLayer.firstChildElement("provider").firstChild().setNodeValue(newDatasourceType)
XMLMapLayers.appendChild(XMLMapLayer)
XMLDocument.appendChild(XMLMapLayers)
layer.readLayerXML(XMLMapLayer)
# Update layer extent
layer.updateExtents()
# Update graduated symbol renderer
if layer.rendererV2().type() == u'graduatedSymbol':
if len(layer.rendererV2().ranges()) == 1:
layer.rendererV2().updateClasses( layer, layer.rendererV2().mode(), len(layer.rendererV2().ranges()) )
#Reload layer
layer.reload()
def splitSource (self,source):
'''
Split QGIS datasource into meaningfull components
'''
if "|" in source:
datasourceType = source.split("|")[0]
uri = source.split("|")[1].replace('\\','/')
else:
datasourceType = None
uri = source.replace('\\','/')
return (datasourceType,uri)
def setDynamicLayerProperties(self, searchAndReplaceDictionary={}):
'''
Set layer title, abstract,
and field aliases (for vector layers only)
'''
layer = self.layer
t = dynamicLayersTools()
# Layer title
# First check that we have a title
sourceTitle = layer.name().strip()
if layer.title().strip() != '':
sourceTitle = layer.title().strip()
if layer.customProperty('titleTemplate') and layer.customProperty('titleTemplate').strip() != '':
sourceTitle = layer.customProperty('titleTemplate').strip()
# Search and replace content
layer.setTitle(
u"%s" % t.searchAndReplaceStringByDictionary(
sourceTitle,
searchAndReplaceDictionary
)
)
# Abstract
sourceAbstract = ''
if layer.abstract().strip() != '':
sourceAbstract = layer.abstract().strip()
if layer.customProperty('abstractTemplate') and layer.customProperty('abstractTemplate').strip() != '':
sourceAbstract = layer.customProperty('abstractTemplate').strip()
layer.setAbstract(
u"%s" % t.searchAndReplaceStringByDictionary(
sourceAbstract,
searchAndReplaceDictionary
)
)
# Set fields aliases
if layer.type() == QgsMapLayer.VectorLayer:
for fid, field in enumerate( layer.pendingFields() ):
alias = layer.attributeAlias( fid )
if not alias:
continue
newAlias = t.searchAndReplaceStringByDictionary(
alias,
searchAndReplaceDictionary
)
layer.addAttributeAlias( fid, newAlias )
class DynamicLayersEngine():
'''
Changes the layers datasource by using dynamicDatasourceContent
as a template and replace variable with data given by the user
'''
# Layer with the location to zoom in
extentLayer = None
# margin around the extent layer
extentMargin = None
# List of dynamic layers
dynamicLayers = {}
# Search and replace dictionnary
searchAndReplaceDictionary = {}
def __init__(
self,
dynamicLayers={},
searchAndReplaceDictionary={},
extentLayer=None,
extentMargin=None
):
'''
Dynamic Layers Engine constructor
'''
self.extentLayer = extentLayer
self.extentMargin = extentMargin
self.dynamicLayers = dynamicLayers
self.searchAndReplaceDictionary = searchAndReplaceDictionary
self.iface = iface
def setExtentLayer( self, layer ):
'''
Set the extent layer.
If a layer is set, the project extent will be changed to this extent
'''
self.extentLayer = layer
def setExtentMargin( self, margin ):
'''
Set the extent margin
'''
margin = int(margin)
if not margin:
return
self.extentMargin = margin
def setSearchAndReplaceDictionary(self, searchAndReplaceDictionary):
'''
Set the search and replace dictionary
'''
self.searchAndReplaceDictionary = searchAndReplaceDictionary
def setSearchAndReplaceDictionaryFromLayer(self, layer, expression):
'''
Set the search and replace dictionary
from a given layer
and an expression.
The first found features is the data source
'''
searchAndReplaceDictionary = {}
# Get and validate expression
qExp = QgsExpression( expression )
if not qExp.hasParserError():
qReq = QgsFeatureRequest( qExp )
features = layer.getFeatures( qReq )
else:
QgsMessageLog.logMessage( 'An error occured while parsing the given expression: %s' % qExp.parserErrorString() )
features = layer.getFeatures()
# Get layer fields name
fields = layer.pendingFields()
field_names = [ field.name() for field in fields ]
# Take only first feature
for feat in features:
# Build dictionnary
searchAndReplaceDictionary = dict(zip(field_names, feat.attributes()))
break
self.searchAndReplaceDictionary = searchAndReplaceDictionary
def setDynamicLayersList( self ):
'''
Add the passed layers to the dynamic layers dictionnary
'''
# Get the layers with dynamicDatasourceActive enable
lr = QgsMapLayerRegistry.instance()
self.dynamicLayers = dict([ (lid,layer) for lid,layer in lr.mapLayers().items() if layer.customProperty('dynamicDatasourceActive') == 'True' and layer.customProperty('dynamicDatasourceContent') ])
def setDynamicLayersDatasourceFromDic(self ):
'''
For each layers with "active" status,
Change the datasource by using the dynamicDatasourceContent
And the given search&replace dictionnary
'''
if not self.searchAndReplaceDictionary or not isinstance(self.searchAndReplaceDictionary, dict):
return
for lid,layer in self.dynamicLayers.items():
# Change datasource
a = layerDataSourceModifier( layer )
a.setNewSourceUriFromDict( self.searchAndReplaceDictionary )
if self.iface and layer.rendererV2().type() == u'graduatedSymbol':
self.iface.legendInterface().refreshLayerSymbology(layer)
if self.iface:
self.iface.actionDraw().trigger()
self.iface.mapCanvas().refresh()
def setDynamicProjectProperties(self, title=None, abstract=None):
'''
Set some project properties : title, abstract
based on the templates stored in the project file in <PluginDynamicLayers>
and by using the search and replace dictionary
'''
# Get project instance
p = QgsProject.instance()
# Make sure WMS Service is active
if not p.readEntry('WMSServiceCapabilities', "/")[1]:
p.writeEntry('WMSServiceCapabilities', "/", "True")
# title
if not title:
xml = 'ProjectTitle'
val = p.readEntry('PluginDynamicLayers' , xml)
if val:
title = val[0]
self.setProjectProperty( 'title', title)
# abstract
if not abstract:
xml = 'ProjectAbstract'
val = p.readEntry('PluginDynamicLayers' , xml)
if val:
abstract = val[0]
self.setProjectProperty( 'abstract', abstract)
def setProjectProperty( self, prop, val):
'''
Set a project property
And replace variable if found in the properties
'''
# Get project instance
p = QgsProject.instance()
# Replace variable in given val via dictionary
t = dynamicLayersTools()
val = t.searchAndReplaceStringByDictionary( val, self.searchAndReplaceDictionary )
# Title
if prop == 'title':
p.writeEntry('WMSServiceTitle', '', u'%s' % val)
# Abstract
elif prop == 'abstract':
p.writeEntry('WMSServiceAbstract', '', u'%s' % val)
def setProjectExtent( self ):
'''
Sets the project extent
and corresponding XML property
'''
p = QgsProject.instance()
# Get extent from extent layer (if given)
pextent = None
if self.extentLayer:
self.extentLayer.updateExtents()
pextent = self.extentLayer.extent()
else:
if self.iface:
pextent = self.iface.mapCanvas().extent()
if pextent and pextent.width() <= 0 and self.iface:
pextent = self.iface.mapCanvas().extent()
# Add a margin
if pextent:
if self.extentMargin:
marginX = pextent.width() * self.extentMargin / 100
marginY = pextent.height() * self.extentMargin / 100
margin = max( marginX, marginY )
pextent = pextent.buffer( margin )
# Modify OWS WMS extent
pWmsExtent = []
pWmsExtent.append(u'%s' % pextent.xMinimum())
pWmsExtent.append(u'%s' % pextent.yMinimum())
pWmsExtent.append(u'%s' % pextent.xMaximum())
pWmsExtent.append(u'%s' % pextent.yMaximum())
p.writeEntry('WMSExtent', '', pWmsExtent)
# Zoom canvas to extent
if self.iface:
iface.mapCanvas().setExtent( pextent )
return pextent