-
Notifications
You must be signed in to change notification settings - Fork 0
/
functions.py
782 lines (605 loc) · 31.1 KB
/
functions.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
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
# -*- coding: utf-8 -*-
# network api functions to be added to the call path registry
#
# the functions should take two arguments: the first is the 'iface' variable
# which gives access to QGIS components, the second a NetworkAPIRequest object.
#
# the functions should return an instance of NetworkAPIResult
from .registry import networkapi, NetworkAPIResult, parseCRS, toGeoJSON
from distutils.util import strtobool
from qgis.core import QgsExpression, QgsFeatureRequest, QgsMapLayerRegistry, QgsPoint, QgsRectangle, QgsVectorFileWriter, QgsRasterLayer
import urllib
# TODO add simple function to simplify wrapping argument-free function calls
@networkapi('/qgis/supportedFiltersAndFormats')
def supportedFiltersAndFormats(_, _2):
"""Returns a dictionary of supported vector formats with format filter string as key and OGR format key as value."""
return NetworkAPIResult(QgsVectorFileWriter.supportedFiltersAndFormats())
# http://qgis.org/api/2.18/classQgisInterface.html
@networkapi('/qgis/defaultStyleSheetOptions')
def defaultStylesheetOptions(iface, _):
"""Return changeable options built from settings and/or defaults."""
return NetworkAPIResult(iface.defaultStyleSheetOptions())
@networkapi('/qgis/addRasterLayer')
def addRasterLayer(iface, request):
"""
Add a new raster layer to QGIS.
The vector data can be provided in two ways:
1. by providing a WMS url as a 'url' GET argument, in combination with a valid 'providerKey'
2. as raster data in the request body of a POST request
3. from a file readable by QGIS, by giving the file name as a 'rasterLayerPath' GET argument. If you are accessing the API from your local machine, this approach saves you from creating a redundant copy of the raster file on disk.
HTTP query arguments:
layerName (string, optional): name for the new layer
rasterLayerPath (string, optional): file name of a locally readable raster data set to be added as a new layer
url (string, optional): url of WMS service to be added as a new layer
providerKey (string, only required together with url argument): QGIS provider key (default: 'ogr')
Returns:
A JSON object containing information on the layer that was just added.
"""
if request.command == 'POST':
# TODO check get_payload() for multipart?
tmpfile, filename = mkstemp()
os.write(tmpfile, request.headers.get_payload())
os.close(tmpfile)
return NetworkAPIResult(iface.addRasterLayer(filename, request.args.get('layerName', '')))
else:
if request.args.get('rasterLayerPath'):
# 2 args for local file: rasterLayerPath, layerName
return NetworkAPIResult(iface.addRasterLayer(request.args['rasterLayerPath'], request.args.get('layerName', '')))
# 3 args for WMS layer: url, name, providerkey
else:
return NetworkAPIResult(iface.addRasterLayer(request.args['url'], request.args.get('layerName', ''), request.args['providerKey']))
@networkapi('/qgis/addTileServerLayer')
def addTileServerLayer(iface, request):
"""
Add a new tile server layer to QGIS.
HTTP query arguments:
name (string): name for the new layer
url (string): url of WMS service to be added as a new layer
providerKey (string): QGIS provider key (default: 'wms')
Returns:
A JSON object containing information on the layer that was just added.
"""
url = request.args['url']
uri = 'type=xyz&url='+ urllib.quote_plus(url)
name = request.args.get('name', '')
provider = request.args['providerKey']
rasterLyr = QgsRasterLayer(uri, name, provider)
return NetworkAPIResult(QgsMapLayerRegistry.instance().addMapLayer(rasterLyr))
@networkapi('/qgis/overwriteTileServerLayer')
def overwriteTileServerLayer(iface, request):
"""
Overwrites tile server layer to QGIS.
HTTP query arguments:
name (string): name for the new layer
url (string): url of WMS service to be added as a new layer
providerKey (string): QGIS provider key (default: 'wms')
Returns:
A JSON object containing information on the layer that was just added.
"""
url = request.args['url']
uri = 'type=xyz&url='+ urllib.quote_plus(url)
name = request.args.get('name', '')
provider = request.args['providerKey']
layer = QgsMapLayerRegistry.instance().mapLayersByName(layer_name)
QgsMapLayerRegistry.instance().removeMapLayers(layer)
rasterLyr = QgsRasterLayer(uri, name, provider)
return NetworkAPIResult(QgsMapLayerRegistry.instance().addMapLayer(rasterLyr))
@networkapi('/qgis/removeLayerByName')
def removeLayerByName(iface, request):
"""
Removes layer from QGIS using provided name.
HTTP query arguments:
name (string): name of layer that will be removed
Returns:
A JSON object containing information on the layer that was just removed.
"""
layer_name = request.args['name']
layer = QgsMapLayerRegistry.instance().mapLayersByName(layer_name)
return NetworkAPIResult(QgsMapLayerRegistry.instance().removeMapLayers(layer))
@networkapi('/qgis/addVectorLayer')
def addVectorLayer(iface, request):
"""
Add a new vector layer to QGIS.
The vector data can be provided in two ways:
1. from an external file, by passing a QGIS provider string as a 'vectorLayerPath' query argument
2. as GeoJSON data in the request body of a POST request (support for other formats to be added later)
HTTP query arguments:
baseName (string, optional): name for the new layer
vectorLayerPath (string, optional): QGIS provider string to a local or external vector data source
providerKey (string, optional): QGIS provider key (default: 'ogr')
crs (string, optional): specification for a coordinate reference system understandable by QGIS, such as a 'EPSG:...' or WKT definition string. This argument is really only useful for the case of adding GeoJSON data (via a POST request), since this is the only format which does not contain information on the coordinate system of the data.
Returns:
A JSON object containing information on the layer that was just added.
"""
if request.command == 'POST':
# TODO check get_payload() for multipart?
tmpfile, filename = mkstemp()
os.write(tmpfile, request.headers.get_payload())
os.close(tmpfile)
# TODO this file should probably not be temporary but actually stay on disk?
else:
# try 'vectorLayerPath' GET arg (could actually be file:// or a web http:// url)
# http://docs.qgis.org/testing/en/docs/pyqgis_developer_cookbook/loadlayer.html#vector-layers
filename = request.args['vectorLayerPath']
layer = iface.addVectorLayer(filename, request.args.get('baseName', ''), request.args.get('providerKey', 'ogr'))
if request.args.get('crs'):
# FIXME what about error handling in such a case? parsing the CRS might
# fail which means the API will return an error, without communicating
# information on the successfully added layer...
layer.setCrs(parseCRS(request.args['crs']))
return NetworkAPIResult(layer)
@networkapi('/qgis/newProject')
def mapLayers_count(iface, _):
"""Start a blank project. Warning: does *not* prompt to save changes to the currently open project!"""
return NetworkAPIResult(iface.newProject())
# http://qgis.org/api/2.18/classQgsMapLayerRegistry.html
@networkapi('/qgis/mapLayers/count')
def mapLayers_count(iface, _):
"""Returns the total number of registered layers, visible or not."""
return NetworkAPIResult(QgsMapLayerRegistry.instance().count())
@networkapi('/qgis/mapLayers')
def mapLayers(iface, request):
"""
Returns information about all registered layers.
For information on the currently visible layers and their ordering, see /qgis/mapCanvas/layers
Returns:
A JSON object containing id : layer pairs.
"""
return NetworkAPIResult(QgsMapLayerRegistry.instance().mapLayers())
# helper function
def qgis_layer_by_id(id):
layer = QgsMapLayerRegistry.instance().mapLayer(str(id))
if layer == None:
raise KeyError('No layer with id: ' + str(id))
return layer
def qgis_layer_by_id_or_current(iface, request):
if request.args.get('id'):
return qgis_layer_by_id(request.args['id'])
if iface.mapCanvas().currentLayer() == None:
raise ValueError('No layer selected in QGIS')
return iface.mapCanvas().currentLayer()
# TODO add support for removal of multiple layers. unwise to overload arg name?
@networkapi('/qgis/mapLayers/removeMapLayer')
def mapLayers_removeMapLayer(iface, request):
"""
Remove a layer from the registry by layer ID.
The specified layer will be removed from the registry. If the registry has ownership of the layer then it will also be deleted.
HTTP query arguments:
id (string): ID of the layer to be removed
"""
layer = qgis_layer_by_id(request.args['id'])
QgsMapLayerRegistry.instance().removeMapLayer(layer)
# 'layer' object is already deleted at this point?
return NetworkAPIResult(request.args['id'])
# http://qgis.org/api/2.18/classQgsMapLayer.html
@networkapi('/qgis/mapLayer')
def mapLayer(_, request):
"""
Return information about the layer with the given ID.
For information about all registered layers and their IDs, see /qgis/mapLayers
HTTP query arguments:
id (string): ID of layer to retrieve
Returns:
A JSON object containing information on the layer with the given ID.
"""
return NetworkAPIResult(qgis_layer_by_id(request.args['id']))
@networkapi('/qgis/mapLayer/crs')
def mapLayer_crs(_, request):
"""Get or set the layer's spatial reference system.
On receiving a POST request, attempts to set the layer's CRS to the content of the request body, which can be any specification for a coordinate reference system understandable by QGIS, such as a 'EPSG:...' or a WKT definition string.
HTTP query arguments:
id (string): ID of the desired layer.
Returns:
A JSON object representing the layer's spatial reference system (after setting)
"""
layer = qgis_layer_by_id(request.args['id'])
if request.command == 'POST':
layer.setCrs(parseCRS(request.headers.get_payload()))
return NetworkAPIResult(layer.crs())
@networkapi('/qgis/mapLayer/featureCount')
def mapLayer_featureCount(iface, request):
"""
Return feature count of the given vector layer.
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
Returns:
Feature count of the given vector layer, including changes which have not yet been committed to this layer's provider.
"""
layer = qgis_layer_by_id_or_current(iface, request)
return NetworkAPIResult(layer.featureCount())
# the following paths require 'id' an argument specifying the desired layer
@networkapi('/qgis/mapLayer/fields')
def mapLayer_fields(_, request):
"""
Returns the list of fields of a layer.
This also includes fields which have not yet been saved to the provider.
HTTP query arguments:
id (string): ID of layer whose fields to retrieve
"""
layer = qgis_layer_by_id(request.args['id'])
return NetworkAPIResult(layer.fields())
@networkapi('/qgis/mapLayer/getFeatures')
def mapLayer_getFeatures(iface, request):
"""
Return information about features of the given vector layer.
Retrieve information about (and, optionally, geometry of) features of the given vector layer by querying the underlying datasource programmatically. To retrieve features that were manually selected by the user within QGIS, see /qgis/mapLayer/selectedFeatures
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
geometry (optional, default false): if true, returns all feature information including their geometry in GeoJSON format. Accepts several string representations of booleans (e.g. 1, 0, true, false, yes, no, ...).
orderBy (optional): expression that the results should be ordered by. If you want to order by a field, you'll have to give its name in quotes, e.g. ?orderBy="length"
ascending (optional, default true): whether the results should be listen in ascending or descending order. Accepts several string representations of booleans (e.g. 1, 0, true, false, yes, no, ...).
nullsfirst (optional): how null values should be treated in the ordering. Accepts several string representations of booleans (e.g. 1, 0, true, false, yes, no, ...).
The different ways to filter features follow the different constructor signatures defined by the QgsFeatureRequest class, in particular:
If the request is a HTTP POST request, the request body is treated as a QGIS filter expression, and the result of applying that filter is returned.
If the request is a HTTP GET request, the following query arguments are considered in order, and the first one provided is applied:
fid (integer): Construct a request with a QGIS feature ID filter
rect (string): Construct a request with a rectangle filter. The rectangle should be specified as four numbers in the format "xmin,ymin,xmax,ymax".
A GET request in which none of the arguments are specified returns ALL features of the given vector layer, which can produce very large results.
Returns:
If the 'geometry' argument was passed: a GeoJSON FeatureCollection with complete attribute and geometry data for all features of the layer.
If the 'geometry' argument was not passed: a list of all features of the vector layer in JSON format, where each feature is an object specifying the feature's 'id' and all its 'attributes'.
"""
layer = qgis_layer_by_id_or_current(iface, request)
# construct and run QgsFeatureRequest depending on arguments
featurerequest = QgsFeatureRequest()
featurerequest.addOrderBy(request.args.get('orderBy', ''), strtobool(request.args.get('ascending', 'y')), strtobool(request.args.get('nullsfirst', 'n')))
if request.command == 'POST':
# POST request: complex QgsExpression passed as string
featurerequest.setFilterExpression(QgsExpression(request.headers.get_payload()))
else:
if request.args.get('fid'):
# query by feature id
featurerequest.setFilterFid(int(request.args['fid']))
elif request.args.get('rect'):
# query by rectangle
r = [float(x) for x in request.args['rect'].split(',')]
if len(r) != 4:
raise ValueError('"rect" argument to getFeatures requires exactly four floats in the format "xmin,ymin,xmax,ymax"')
featurerequest.setFilterRect(QgsRectangle(r[0], r[1], r[2], r[3]))
result = layer.getFeatures(featurerequest)
if strtobool(request.args.get('geometry', 'n')):
return NetworkAPIResult(toGeoJSON(layer, result), 'application/geo+json; charset=utf-8')
else:
# note that the lazy QgsFeatureIterator returned here is currently
# turned into a full in-memory list during conversion to JSON
return NetworkAPIResult(result)
@networkapi('/qgis/mapLayer/selectedFeatureCount')
def mapLayer_selectedFeatureCount(iface, request):
"""
Return the number of features that are selected in the given vector layer.
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
Returns:
An integer indicating the number of items that would be returned by the corresponding call to /qgis/mapLayer/selectedFeatures
"""
layer = qgis_layer_by_id_or_current(iface, request)
return NetworkAPIResult(layer.selectedFeatureCount())
@networkapi('/qgis/mapLayer/selectedFeatures')
def mapLayer_selectedFeatures(iface, request):
"""
Return information about the currently selected features from the given vector layer.
Returns ids and attributes of the features only. In order to also retrieve the geometry as well as layer information of the features (in proper GeoJSON), see /qgis/mapLayer/selectedFeatures/geometry
To retrieve *all* features of the layer that are available from its provider, see /qgis/mapLayer/getFeatures
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
Returns:
A list of all currently selected features in JSON format, where each feature is an object specifying the feature's 'id' and all its 'attributes'.
"""
layer = qgis_layer_by_id_or_current(iface, request)
# FIXME this sometimes returns empty attributes and id of 0 for some
# elements, maybe retrieve features via their id instead?
return NetworkAPIResult(layer.selectedFeatures())
@networkapi('/qgis/mapLayer/selectedFeatures/geometry')
def mapLayer_selectedFeatures_geometry(iface, request):
"""
Return attributes and geometry of the currently selected features from the given vector layer.
To inspect the feature ids and attributes only, see /qgis/mapLayer/selectedFeatures
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
Returns:
A GeoJSON FeatureCollection with complete data of all selected features of the given vector layer.
"""
layer = qgis_layer_by_id_or_current(iface, request)
return NetworkAPIResult(toGeoJSON(layer, layer.selectedFeatures()), 'application/geo+json; charset=utf-8')
@networkapi('/qgis/mapLayer/setAttribution')
def mapLayer_setAttribution(iface, request):
"""Set the attribution of the layer used by QGIS Server in GetCapabilities request.
Attribution indicates the provider of a Layer or collection of Layers.
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
attrib (string): the desired attribution for the layer
Returns:
NULL
"""
layer = qgis_layer_by_id_or_current(iface, request)
return NetworkAPIResult(layer.setAttribution(request.args['attrib']))
@networkapi('/qgis/mapLayer/subLayers')
def mapLayer_subLayers(iface, request):
"""Returns the sublayers of this layer (Useful for providers that manage their own layers, such as WMS)
HTTP query arguments:
id (optional): ID of layer from which selected features should be retrieved. If not specified, defaults to the currently active layer.
Returns:
A list of sublayer ids
"""
layer = qgis_layer_by_id_or_current(iface, request)
return NetworkAPIResult(layer.subLayers())
@networkapi('/qgis/mapLayer/visible')
def mapLayer_visible(iface, request):
"""Retrieve or set layer visibility (i.e. if it is rendered to the map canvas)
HTTP query arguments:
id (string): ID of layer whose visibility to retrieve or set
Returns:
The visibility status of the layer after the operation ('true' or 'false')
"""
layer = qgis_layer_by_id(request.args['id'])
if request.command == 'POST':
iface.legendInterface().setLayerVisible(layer, request.headers.get_payload())
return NetworkAPIResult(iface.legendInterface().isLayerVisible(layer))
@networkapi('/qgis/legendInterface/layers')
def legendInterface_layers(iface, _):
"""Return all layers in the project in drawing order.
Returns:
A JSON object containing id : layer pairs.
"""
return NetworkAPIResult(iface.legendInterface().layers())
# iface.layerTreeCanvasBridge().setCustomLayerOrder( order )
@networkapi('/qgis/legendInterface/currentLayer')
def legendInterface_currentLayer(iface, _):
"""Retrieve the current layer in QGIS' layers panel.
Returns:
A JSON object containing information on the current layer (or 'null' if none is selected)
"""
return NetworkAPIResult(iface.legendInterface().currentLayer())
@networkapi('/qgis/legendInterface/setCurrentLayer')
def legendInterface_setCurrentLayer(iface, request):
"""Set the current layer in QGIS' layers panel.
HTTP query arguments:
id (string): ID of layer to set current
Returns:
'true' if the layer exists and could be set as the current layer, 'false' otherwise
"""
return NetworkAPIResult(iface.legendInterface().setCurrentLayer(qgis_layer_by_id(request.args['id'])))
from PyQt4.QtXml import QDomDocument
@networkapi('/qgis/mapLayer/xml')
def mapLayer_xml(_, request):
"""
Retrieve information about the layer with the given id.
HTTP query arguments:
id (string): ID of layer whose definition to retrieve or set
Returns:
The XML definition for the layer with the given ID.
"""
layer = qgis_layer_by_id(request.args['id'])
doc = QDomDocument('xml')
root = doc.createElement('maplayer')
doc.appendChild(root)
layer.writeLayerXML(root, doc, '')
# QDomDocument is automatically processed by server
return NetworkAPIResult(doc)
# http://qgis.org/api/2.18/classQgsMapCanvas.html
import os
from tempfile import mkstemp
from PyQt4.QtGui import QPixmap
@networkapi('/qgis/mapCanvas')
def mapCanvas(iface, _):
"""
Return configuration and properties used for map rendering, such as visible area, projection etc.
To obtain the XML specification of the configurable map canvas settings, see /qgis/mapCanvas/xml
Unlike the XML configuration, this path also returns information about properties of the canvas which are to do with the underlying layers or are incidental to the current session (such as 'fullExtent' and 'visibleExtent').
Returns:
The contents of the canvas' QgsMapSettings object in JSON format.
"""
# TODO implement access to specific fields via GET arg?
return NetworkAPIResult(iface.mapCanvas().mapSettings())
@networkapi('/qgis/mapCanvas/xml')
def mapCanvas_xml(iface, _):
"""
Return XML configuration used for map rendering.
To obtain a JSON specification of the current map canvas settings, see /qgis/mapCanvas
Returns:
The XML serialisation of the canvas' QgsMapSettings object.
"""
# TODO implement POST command
doc = QDomDocument('xml')
root = doc.createElement('mapcanvas')
doc.appendChild(root)
iface.mapCanvas().mapSettings().writeXML(root, doc)
# QDomDocument is automatically processed by server
return NetworkAPIResult(doc)
# to be able to block while awaiting redraw-signal
from PyQt4.QtCore import QEventLoop
@networkapi('/qgis/mapCanvas/saveAsImage')
def mapCanvas_saveAsImage(iface, request):
"""
Return the currently visible content of the map canvas as an image.
HTTP query arguments:
format (optional): any image format string supported by QGIS' (default: 'png', other options e.g. 'jpeg')
Returns:
An image the same size as the currently visible map canvas. The content-type of the response is set to 'image/' + format.
"""
ext = request.args.get('format', 'png')
tmpfile, tmpfilename = mkstemp('.' + ext)
os.close(tmpfile)
# if either is unspecified or otherwise invalid, QGIS display size is used
pixmap = QPixmap(int(request.args.get('width', 0)), int(request.args.get('height', 0)))
if pixmap.isNull():
pixmap = None
# if the canvas has been modified very recently, it might not be done
# re-rendering yet... current fix (from QGIS 3 we could simply use:
# iface.mapCanvas().waitWhileRendering()
loop = QEventLoop()
iface.mapCanvas().mapCanvasRefreshed.connect(loop.quit)
# trigger repaint
iface.mapCanvas().refresh() # could use refreshAllLayers() to empty cache too
# wait
loop.exec_()
# all good
iface.mapCanvas().mapCanvasRefreshed.disconnect(loop.quit)
# doesn't provide status information about success of writing, have to
# check file size below
iface.mapCanvas().saveAsImage(tmpfilename, pixmap, ext)
with open(tmpfilename, 'r') as content_file:
imagedata = content_file.read()
os.remove(tmpfilename)
# TODO QGIS also writes a 'world file' (same filename + 'w' at the end)?
if len(imagedata) == 0:
raise IOError('Failed to write canvas contents in format ' + ext)
# FIXME format = 'jpg' generates image correctly but has incorrect MIMEtype
return NetworkAPIResult(imagedata, content_type='image/' + ext.lower())
@networkapi('/qgis/mapCanvas/center')
def mapCanvas_center(iface, _):
"""Get map center, in geographical coordinates."""
return NetworkAPIResult(iface.mapCanvas().center())
@networkapi('/qgis/mapCanvas/setCenter')
def mapCanvas_setCenter(iface, request):
"""Set the center of the map canvas, in geographical coordinates.
HTTP query arguments:
x (float): new x coordinate of the map canvas center
y (float): new y coordinate of the map canvas center
"""
center = QgsPoint(float(request.args['x']), float(request.args['y']))
iface.mapCanvas().setCenter(center)
return NetworkAPIResult(iface.mapCanvas().center())
@networkapi('/qgis/mapCanvas/crsTransformEnabled')
def mapCanvas_crsTransformEnabled(iface, request):
"""Get or set whether on-the-fly reprojection is enabled or disabled.
Returns: whether on-the-fly reprojection is currently enabled or not.
If the request is a POST request, parses the new on/off setting (as a JSON-encoded boolean or number) from the request body.
"""
if request.command == 'POST':
iface.mapCanvas().setCrsTransformEnabled(request.headers.get_payload())
return NetworkAPIResult(iface.mapCanvas().hasCrsTransformEnabled())
from qgis.core import QgsCoordinateReferenceSystem
@networkapi('/qgis/mapCanvas/destinationCrs')
def mapCanvas_destinationCrs(iface, request):
"""Get or set the map canvas' destination coordinate reference system.
If the request is a POST request, sets the destination CRS to that specified by the request body. Supports any specification understandable by QGIS, such as a 'EPSG:...' or a WKT or proj.4 definition string.
Returns:
A JSON object representing the map canvas' coordinate reference system after applying the given definition string.
"""
if request.command == 'POST':
crs = parseCRS(request.headers.get_payload())
# don't use mapSettings.setDestinationCrs() directly but call the map
# canvas' function, which does some additional checking first
iface.mapCanvas().setDestinationCrs(crs)
return NetworkAPIResult(iface.mapCanvas().mapSettings().destinationCrs())
@networkapi('/qgis/mapCanvas/extent')
def mapCanvas_extent(iface, _):
"""Returns the current zoom exent of the map canvas."""
return NetworkAPIResult(iface.mapCanvas().extent())
@networkapi('/qgis/mapCanvas/fullExtent')
def mapCanvas_fullExtent(iface, _):
"""Returns the combined exent for all layers on the map canvas."""
return NetworkAPIResult(iface.mapCanvas().fullExtent())
@networkapi('/qgis/mapCanvas/layer')
def mapCanvas_layer(iface, request):
"""
Return the map layer at position index in the layer stack.
HTTP query arguments:
index (int): position index in the layer stack, between 0 and layerCount-1
"""
layer = iface.mapCanvas().layer(int(request.args['index']))
if layer:
return NetworkAPIResult(layer)
else:
raise KeyError('Invalid layer index: ' + str(int(request.args['index'])))
@networkapi('/qgis/mapCanvas/layerCount')
def mapCanvas_layerCount(iface, _):
"""
Return number of layers on the map that are set visible.
For the total number of registered layers, see /qgis/mapLayers/count
"""
return NetworkAPIResult(iface.mapCanvas().layerCount())
@networkapi('/qgis/mapCanvas/layers')
def mapCanvas_layers(iface, _):
"""
Return list of layers within map canvas that are set visible.
For *all* registered layers, see /qgis/mapLayers
"""
return NetworkAPIResult(iface.mapCanvas().layers())
@networkapi('/qgis/mapCanvas/magnificationFactor')
def mapCanvas_magnificationFactor(iface, _):
"""Returns the magnification factor."""
return NetworkAPIResult(iface.mapCanvas().magnificationFactor())
@networkapi('/qgis/mapCanvas/setMagnificationFactor')
def mapCanvas_setMagnificationFactor(iface, request):
"""
Sets the factor of magnification to apply to the map canvas.
HTTP query arguments:
factor (float): target magnification factor
"""
return NetworkAPIResult(iface.mapCanvas().setMagnificationFactor(float(request.args['factor'])))
@networkapi('/qgis/mapCanvas/scale')
def mapCanvas_scale(iface, _):
"""Get the last reported scale of the canvas.
Returns:
The last reported scale of the canvas (a single float)
"""
return NetworkAPIResult(iface.mapCanvas().scale())
@networkapi('/qgis/mapCanvas/zoomIn')
def mapCanvas_zoomIn(iface, _):
"""
Zoom in with fixed factor.
Returns:
The new scale of the canvas (a single float)
"""
iface.mapCanvas().zoomIn()
return NetworkAPIResult(iface.mapCanvas().scale())
@networkapi('/qgis/mapCanvas/zoomOut')
def mapCanvas_zoomOut(iface, _):
"""
Zoom out with fixed factor.
Returns:
The new scale of the canvas (a single float)
"""
iface.mapCanvas().zoomOut()
return NetworkAPIResult(iface.mapCanvas().scale())
@networkapi('/qgis/mapCanvas/zoomScale')
def mapCanvas_zoomScale(iface, request):
"""
Zoom to a specific scale.
HTTP query arguments:
scale (float): target scale
Returns:
The new scale of the canvas (a single float)
"""
iface.mapCanvas().zoomScale(float(request.args['scale']))
return NetworkAPIResult(iface.mapCanvas().scale())
@networkapi('/qgis/mapCanvas/setExtent')
def mapCanvas_setExtent(iface, request):
"""
Zoom to feature extent and redraw map canvas.
Adds a small margin around the extent and does a pan if rect is empty (point extent).
HTTP query arguments:
rect (string): Feature extent, specified as four numbers in the format "xmin,ymin,xmax,ymax".
Returns:
The new scale of the canvas (a single float)
"""
ext = [float(x) for x in request.args['rect'].split(',')]
iface.mapCanvas().setExtent(QgsRectangle(ext[0], ext[1], ext[2], ext[3]))
# force redraw
iface.mapCanvas().refresh()
return NetworkAPIResult(iface.mapCanvas().scale())
@networkapi('/qgis/mapCanvas/zoomToFullExtent')
def mapCanvas_zoomToFullExtent(iface, _):
"""
Zoom to the full extent of all layers.
Returns:
The new scale of the canvas (a single float)
"""
iface.mapCanvas().zoomToFullExtent()
return NetworkAPIResult(iface.mapCanvas().scale())
@networkapi('/qgis/mapCanvas/zoomToSelected')
def mapCanvas_zoomToSelected(iface, request):
"""
Zoom to the extent of the selected features of current (vector) layer.
HTTP query arguments:
layer (string): optionally specify different than current layer
Returns:
The new scale of the canvas (a single float)
"""
if request.args.get('layer'):
iface.mapCanvas().zoomToSelected(qgis_layer_by_id(request.args['layer']))
else:
iface.mapCanvas().zoomToSelected()
return NetworkAPIResult(iface.mapCanvas().scale())