├── LICENSE
├── README.md
├── imgs
├── screenshot01.png
├── screenshot03.png
└── screenshot06.png
└── postgis_viewer
├── changelog.txt
├── imgs
├── collapse.png
├── expand.png
├── mActionPan.png
├── mActionZoomFullExtent.png
├── mActionZoomIn.png
├── mActionZoomOut.png
├── mActionZoomToLayer.png
├── mIconLineLayer.png
├── mIconPointLayer.png
├── mIconPolygonLayer.png
├── removeLayer.png
└── symbology.png
├── plugins
└── FastSQLlayer
│ ├── __init__.py
│ ├── highlighter.py
│ ├── icon.png
│ ├── postgis_utils.py
│ ├── postgislayer.py
│ ├── readme
│ ├── resources.py
│ ├── resources.qrc
│ └── ui_postgislayer.ui
├── postgis_viewer.bat
└── postgis_viewer.py
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PostGIS Layer Viewer
2 | A PostGIS layer viewer for pgAdmin3, based on PyQt4 and PyQGIS libs.
3 |
4 | *License*: GNU/GPL v.2.0
5 |
6 | *Current version*: 1.6.1 (2015.02.24)
7 |
8 | With this pgAdmin3 plugin you can:
9 |
10 | * View PostGIS layers. It supports both vector and raster layers.
11 |
12 | * Get layer metadata such as connection string, vector/raster type, SRS, Extent, Width-Height in pixels (for rasters), number of bands (for rasters), number of features (for vectors), number of fields (for vectors).
13 |
14 | * Run and visualize PostGIS queries.
15 |
16 | Installation and usage instructions at [GeoTux](https://geotux.tuxfamily.org/en/2011/02/24/postgis-viewer-for-pgadmin-3/).
17 |
18 | **Screenshots**
19 |
20 | pgAdmin's GUI
21 |
22 | ![pgAdminGUI][1]
23 |
24 | Raster support
25 |
26 | ![Load Them All][2]
27 |
28 | Run and visualize your PostGIS queries
29 |
30 | ![Load Them All][3]
31 |
32 | See the changelog at https://github.com/gacarrillor/postgis-layer-viewer/blob/master/postgis_viewer/changelog.txt
33 |
34 | [1]: ./imgs/screenshot01.png
35 | [2]: ./imgs/screenshot06.png
36 | [3]: ./imgs/screenshot03.png
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/imgs/screenshot01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/imgs/screenshot01.png
--------------------------------------------------------------------------------
/imgs/screenshot03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/imgs/screenshot03.png
--------------------------------------------------------------------------------
/imgs/screenshot06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/imgs/screenshot06.png
--------------------------------------------------------------------------------
/postgis_viewer/changelog.txt:
--------------------------------------------------------------------------------
1 | ================================================================================
2 | v.1.6.1 (2015.02.24)
3 | + Bug fixes for running PostGIS queries and loading results to the map.
4 | + Source code published on GitHub.
5 |
6 | ================================================================================
7 | v.1.6 (2014.12.08)
8 | + Support for PostGIS raster layers updated to QGIS 2.6.
9 | + Replacement of deprecated methods/properties:
10 | fieldCount() --> fields().count()
11 | + Old symbology removed.
12 |
13 | ================================================================================
14 | v.1.5 (2012.03.20)
15 | + Geography type supported
16 |
17 | ================================================================================
18 | v.1.4 (2012.02.17)
19 | + Zoom full extent added
20 | + This changelog was included in the zip file to keep track of versions
21 |
22 | ================================================================================
23 | v.1.3.1 (2012.02.17)
24 | + Minor update in FastSQLLayer to remove ";" in SQL queries
25 |
26 | ================================================================================
27 | v.1.3 (2012.02.04)
28 | + Replacement of deprecated methods/properties:
29 | getLayerID() --> id()
30 | srs -> crs
31 | + Basic support for symbology-NG
32 | + Log message about SRS fixed (Now it shows more useful description about the EPSG code)
33 |
34 | FastSQLLayer plugin added:
35 | Changes to the GUI to fit in small space
36 | Show Error Messages for checking syntaxis:
37 | Use Dobias' postgis_utils.py rather than the connection module of the FastSQLLayer plugin
38 | Read the SRID from the query's layer to show it on the TOC (postgis_utils.py modified)
39 |
40 | ================================================================================
41 |
--------------------------------------------------------------------------------
/postgis_viewer/imgs/collapse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/collapse.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/expand.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/expand.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mActionPan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mActionPan.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mActionZoomFullExtent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mActionZoomFullExtent.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mActionZoomIn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mActionZoomIn.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mActionZoomOut.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mActionZoomOut.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mActionZoomToLayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mActionZoomToLayer.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mIconLineLayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mIconLineLayer.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mIconPointLayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mIconPointLayer.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/mIconPolygonLayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/mIconPolygonLayer.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/removeLayer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/removeLayer.png
--------------------------------------------------------------------------------
/postgis_viewer/imgs/symbology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/imgs/symbology.png
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | /***************************************************************************
3 | Fast SQL Layer
4 | A QGIS plugin
5 | Just type the query to add the layer, for experienced users
6 | -------------------
7 | begin : 2011-05-12
8 | copyright : (C) 2011 by Pablo Torres Carreira
9 | email : pablotcarreira@hotmail.com
10 | ***************************************************************************/
11 |
12 | /***************************************************************************
13 | * *
14 | * This program is free software; you can redistribute it and/or modify *
15 | * it under the terms of the GNU General Public License as published by *
16 | * the Free Software Foundation; either version 2 of the License, or *
17 | * (at your option) any later version. *
18 | * *
19 | ***************************************************************************/
20 | This script initializes the plugin, making it known to QGIS.
21 | """
22 | def name():
23 | return "Fast SQL layer"
24 | def description():
25 | return "Dockable SQL editor with code highlighting that adds query layers"
26 | def version():
27 | return "Version 0.2.3"
28 | def icon():
29 | return "icon.png"
30 | def qgisMinimumVersion():
31 | return "1.7"
32 | def classFactory(iface, host=None, port=None, dbname=None, user=None, passwd=None):
33 | # load PostgisLayer class from file PostgisLayer
34 | from postgislayer import PostgisLayer
35 | return PostgisLayer(iface, host, port, dbname, user, passwd)
36 |
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/highlighter.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import sys
3 | import re
4 | from PyQt4 import QtCore, QtGui
5 | from pygments import highlight
6 | from pygments.lexers import *
7 | from pygments.formatter import Formatter
8 | import time
9 |
10 | # Copyright (C) 2008 Christophe Kibleur
11 | #
12 | # This file is part of WikiParser (http://thewikiblog.appspot.com/).
13 | #
14 |
15 | def hex2QColor(c):
16 | r=int(c[0:2],16)
17 | g=int(c[2:4],16)
18 | b=int(c[4:6],16)
19 | return QtGui.QColor(r,g,b)
20 |
21 |
22 |
23 | class QFormatter(Formatter):
24 |
25 | def __init__(self):
26 | Formatter.__init__(self)
27 | self.data=[]
28 |
29 | # Create a dictionary of text styles, indexed
30 | # by pygments token names, containing QTextCharFormat
31 | # instances according to pygments' description
32 | # of each style
33 |
34 | self.styles={}
35 | for token, style in self.style:
36 | qtf=QtGui.QTextCharFormat()
37 |
38 | if style['color']:
39 | qtf.setForeground(hex2QColor(style['color']))
40 | if style['bgcolor']:
41 | qtf.setBackground(hex2QColor(style['bgcolor']))
42 | if style['bold']:
43 | qtf.setFontWeight(QtGui.QFont.Bold)
44 | if style['italic']:
45 | qtf.setFontItalic(True)
46 | if style['underline']:
47 | qtf.setFontUnderline(True)
48 | self.styles[str(token)]=qtf
49 |
50 | def format(self, tokensource, outfile):
51 | global styles
52 | # We ignore outfile, keep output in a buffer
53 | self.data=[]
54 |
55 | # Just store a list of styles, one for each character
56 | # in the input. Obviously a smarter thing with
57 | # offsets and lengths is a good idea!
58 |
59 | for ttype, value in tokensource:
60 | l=len(value)
61 | t=str(ttype)
62 | self.data.extend([self.styles[t],]*l)
63 |
64 |
65 | class Highlighter(QtGui.QSyntaxHighlighter):
66 |
67 | def __init__(self, parent, mode):
68 | QtGui.QSyntaxHighlighter.__init__(self, parent)
69 | self.tstamp=time.time()
70 |
71 | # Keep the formatter and lexer, initializing them
72 | # may be costly.
73 | self.formatter=QFormatter()
74 | self.lexer=get_lexer_by_name(mode)
75 |
76 | def highlightBlock(self, text):
77 | """Takes a block, applies format to the document.
78 | according to what's in it.
79 | """
80 |
81 | # I need to know where in the document we are,
82 | # because our formatting info is global to
83 | # the document
84 | cb = self.currentBlock()
85 | p = cb.position()
86 |
87 | # The \n is not really needed, but sometimes
88 | # you are in an empty last block, so your position is
89 | # **after** the end of the document.
90 | text=unicode(self.document().toPlainText())+'\n'
91 |
92 | # Yes, re-highlight the whole document.
93 | # There **must** be some optimizacion possibilities
94 | # but it seems fast enough.
95 | highlight(text,self.lexer,self.formatter)
96 |
97 | # Just apply the formatting to this block.
98 | # For titles, it may be necessary to backtrack
99 | # and format a couple of blocks **earlier**.
100 | for i in range(len(unicode(text))):
101 | try:
102 | self.setFormat(i,1,self.formatter.data[p+i])
103 | except IndexError:
104 | pass
105 |
106 | # I may need to do something about this being called
107 | # too quickly.
108 | self.tstamp=time.time()
109 |
110 |
111 | ## if __name__ == "__main__":
112 | ## app = QtGui.QApplication(sys.argv)
113 |
114 | ## rst = QtGui.QPlainTextEdit()
115 | ## rst.setWindowTitle('reSt')
116 | ## hl=Highlighter(rst.document(),"rest")
117 | ## rst.show()
118 |
119 | ## python = QtGui.QPlainTextEdit()
120 | ## python.setWindowTitle('python')
121 | ## hl=Highlighter(python.document(),"python")
122 | ## python.show()
123 |
124 | ## sys.exit(app.exec_())
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gacarrillor/postgis-layer-viewer/68fb344046b07414101596a788febdf8fd372b8b/postgis_viewer/plugins/FastSQLlayer/icon.png
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/postgis_utils.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | RT Sql Layer
4 | Copyright 2010 Giuseppe Sucameli
5 |
6 | based on PostGIS Manager
7 | Copyright 2008 Martin Dobias
8 |
9 | Licensed under the terms of GNU GPL v2 (or any layer)
10 | http://www.gnu.org/copyleft/gpl.html
11 |
12 |
13 | Good resource for metadata extraction:
14 | http://www.alberton.info/postgresql_meta_info.html
15 | System information functions:
16 | http://www.postgresql.org/docs/8.0/static/functions-info.html
17 | """
18 |
19 | import psycopg2
20 | import psycopg2.extensions # for isolation levels
21 | import re
22 |
23 | # use unicode!
24 | psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
25 |
26 | class TableAttribute:
27 | def __init__(self, row):
28 | self.num, self.name, self.data_type, self.char_max_len, self.modifier, self.notnull, self.hasdefault, self.default = row
29 |
30 |
31 | class TableConstraint:
32 | """ class that represents a constraint of a table (relation) """
33 |
34 | TypeCheck, TypeForeignKey, TypePrimaryKey, TypeUnique = range(4)
35 | types = { "c" : TypeCheck, "f" : TypeForeignKey, "p" : TypePrimaryKey, "u" : TypeUnique }
36 |
37 | on_action = { "a" : "NO ACTION", "r" : "RESTRICT", "c" : "CASCADE", "n" : "SET NULL", "d" : "SET DEFAULT" }
38 | match_types = { "u" : "UNSPECIFIED", "f" : "FULL", "p" : "PARTIAL" }
39 |
40 | def __init__(self, row):
41 | self.name, con_type, self.is_defferable, self.is_deffered, keys = row[:5]
42 | self.keys = map(int, keys.split(' '))
43 | self.con_type = TableConstraint.types[con_type] # convert to enum
44 | if self.con_type == TableConstraint.TypeCheck:
45 | self.check_src = row[5]
46 | elif self.con_type == TableConstraint.TypeForeignKey:
47 | self.foreign_table = row[6]
48 | self.foreign_on_update = TableConstraint.on_action[row[7]]
49 | self.foreign_on_delete = TableConstraint.on_action[row[8]]
50 | self.foreign_match_type = TableConstraint.match_types[row[9]]
51 | self.foreign_keys = row[10]
52 |
53 |
54 | class TableIndex:
55 |
56 | def __init__(self, row):
57 | self.name, columns = row
58 | self.columns = map(int, columns.split(' '))
59 |
60 |
61 | class TableTrigger:
62 |
63 | # Bits within tgtype (pg_trigger.h)
64 | TypeRow = (1 << 0) # row or statement
65 | TypeBefore = (1 << 1) # before or after
66 | # events: one or more
67 | TypeInsert = (1 << 2)
68 | TypeDelete = (1 << 3)
69 | TypeUpdate = (1 << 4)
70 | TypeTruncate = (1 << 5)
71 |
72 | def __init__(self, row):
73 | self.name, self.function, self.type, self.enabled = row
74 |
75 |
76 | class TableRule:
77 |
78 | def __init__(self, row):
79 | self.name, self.definition = row
80 |
81 |
82 | class DbError(Exception):
83 | def __init__(self, error):
84 | # save error. funny that the variables are in utf8, not
85 | self.msg = unicode( error.args[0], 'utf-8')
86 | self.a = error.args[0]
87 | if hasattr(error, "cursor") and hasattr(error.cursor, "query"):
88 | self.query = unicode(str(error.cursor.query), 'utf-8')
89 | else:
90 | self.query = None
91 |
92 | def __str__(self):
93 | if self.query is None:
94 | return self.msg.encode('utf-8')
95 | return self.msg.encode('utf-8') + "\nQuery:\n" + self.query.encode('utf-8')
96 |
97 |
98 | class TableField:
99 | def __init__(self, name, data_type, is_null=None, default=None, modifier=None):
100 | self.name, self.data_type, self.is_null, self.default, self.modifier = name, data_type, is_null, default, modifier
101 |
102 | def is_null_txt(self):
103 | if self.is_null:
104 | return "NULL"
105 | else:
106 | return "NOT NULL"
107 |
108 | def field_def(self, db):
109 | """ return field definition as used for CREATE TABLE or ALTER TABLE command """
110 | data_type = self.data_type if (not self.modifier or self.modifier < 0) else "%s(%d)" % (self.data_type, self.modifier)
111 | txt = "%s %s %s" % (db._quote(self.name), data_type, self.is_null_txt())
112 | if self.default and len(self.default) > 0:
113 | txt += " DEFAULT %s" % self.default
114 | return txt
115 |
116 |
117 | class GeoDB:
118 |
119 | def __init__(self, host=None, port=None, dbname=None, user=None, passwd=None):
120 |
121 | self.host = host
122 | self.port = port
123 | self.dbname = dbname
124 | self.user = user
125 | self.passwd = passwd
126 |
127 | if self.dbname == '' or self.dbname is None:
128 | self.dbname = self.user
129 |
130 | try:
131 | self.con = psycopg2.connect(self.con_info())
132 | except psycopg2.OperationalError, e:
133 | raise DbError(e)
134 |
135 | self.has_postgis = self.check_postgis()
136 |
137 | self.check_geometry_columns_table()
138 |
139 | # a counter to ensure that the cursor will be unique
140 | self.last_cursor_id = 0
141 |
142 | def con_info(self):
143 | con_str = ''
144 | if self.host: con_str += "host='%s' " % self.host
145 | if self.port: con_str += "port=%d " % self.port
146 | if self.dbname: con_str += "dbname='%s' " % self.dbname
147 | if self.user: con_str += "user='%s' " % self.user
148 | if self.passwd: con_str += "password='%s' " % self.passwd
149 | return con_str
150 |
151 | def get_info(self):
152 | c = self.con.cursor()
153 | self._exec_sql(c, "SELECT version()")
154 | return c.fetchone()[0]
155 |
156 | def check_postgis(self):
157 | """ check whether postgis_version is present in catalog """
158 | c = self.con.cursor()
159 | self._exec_sql(c, "SELECT COUNT(*) FROM pg_proc WHERE proname = 'postgis_version'")
160 | return (c.fetchone()[0] > 0)
161 |
162 | def get_postgis_info(self):
163 | """ returns tuple about postgis support:
164 | - lib version
165 | - installed scripts version
166 | - released scripts version
167 | - geos version
168 | - proj version
169 | - whether uses stats
170 | """
171 | c = self.con.cursor()
172 | self._exec_sql(c, "SELECT postgis_lib_version(), postgis_scripts_installed(), postgis_scripts_released(), postgis_geos_version(), postgis_proj_version(), postgis_uses_stats()")
173 | return c.fetchone()
174 |
175 | def check_geometry_columns_table(self):
176 |
177 | c = self.con.cursor()
178 | self._exec_sql(c, "SELECT relname FROM pg_class WHERE relname = 'geometry_columns' AND pg_class.relkind IN ('v', 'r')")
179 | self.has_geometry_columns = (len(c.fetchall()) != 0)
180 |
181 | if not self.has_geometry_columns:
182 | self.has_geometry_columns_access = False
183 | return
184 |
185 | # find out whether has privileges to access geometry_columns table
186 | self.has_geometry_columns_access = self.get_table_privileges('geometry_columns')[0]
187 |
188 |
189 | def list_schemas(self):
190 | """
191 | get list of schemas in tuples: (oid, name, owner, perms)
192 | """
193 | c = self.con.cursor()
194 | sql = "SELECT oid, nspname, pg_get_userbyid(nspowner), nspacl FROM pg_namespace WHERE nspname !~ '^pg_' AND nspname != 'information_schema'"
195 | self._exec_sql(c, sql)
196 |
197 | schema_cmp = lambda x,y: -1 if x[1] < y[1] else 1
198 |
199 | return sorted(c.fetchall(), cmp=schema_cmp)
200 |
201 | def list_geotables(self, schema=None):
202 | """
203 | get list of tables with schemas, whether user has privileges, whether table has geometry column(s) etc.
204 |
205 | geometry_columns:
206 | - f_table_schema
207 | - f_table_name
208 | - f_geometry_column
209 | - coord_dimension
210 | - srid
211 | - type
212 | """
213 | c = self.con.cursor()
214 |
215 | if schema:
216 | schema_where = " AND nspname = '%s' " % self._quote_str(schema)
217 | else:
218 | schema_where = " AND (nspname != 'information_schema' AND nspname !~ 'pg_') "
219 |
220 | # LEFT OUTER JOIN: like LEFT JOIN but if there are more matches, for join, all are used (not only one)
221 |
222 | # first find out whether postgis is enabled
223 | if not self.has_postgis:
224 | # get all tables and views
225 | sql = """SELECT pg_class.relname, pg_namespace.nspname, pg_class.relkind, pg_get_userbyid(relowner), reltuples, relpages, NULL, NULL, NULL, NULL
226 | FROM pg_class
227 | JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
228 | WHERE pg_class.relkind IN ('v', 'r')""" + schema_where + "ORDER BY nspname, relname"
229 | else:
230 | # discovery of all tables and whether they contain a geometry column
231 | sql = """SELECT pg_class.relname, pg_namespace.nspname, pg_class.relkind, pg_get_userbyid(relowner), reltuples, relpages, pg_attribute.attname, pg_attribute.atttypid::regtype, NULL, NULL
232 | FROM pg_class
233 | JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace
234 | LEFT OUTER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid AND
235 | ( pg_attribute.atttypid = 'geometry'::regtype
236 | OR pg_attribute.atttypid IN (SELECT oid FROM pg_type WHERE typbasetype='geometry'::regtype ) )
237 | WHERE pg_class.relkind IN ('v', 'r')""" + schema_where + "ORDER BY nspname, relname, attname"
238 |
239 | self._exec_sql(c, sql)
240 | items = c.fetchall()
241 |
242 | # get geometry info from geometry_columns if exists
243 | if self.has_postgis and self.has_geometry_columns and self.has_geometry_columns_access:
244 | sql = """SELECT relname, nspname, relkind, pg_get_userbyid(relowner), reltuples, relpages,
245 | geometry_columns.f_geometry_column, geometry_columns.type, geometry_columns.coord_dimension, geometry_columns.srid
246 | FROM pg_class
247 | JOIN pg_namespace ON relnamespace=pg_namespace.oid
248 | LEFT OUTER JOIN geometry_columns ON relname=f_table_name AND nspname=f_table_schema
249 | WHERE (relkind = 'r' or relkind='v') """ + schema_where + "ORDER BY nspname, relname, f_geometry_column"
250 | self._exec_sql(c, sql)
251 |
252 | # merge geometry info to "items"
253 | for i, geo_item in enumerate(c.fetchall()):
254 | if geo_item[7]:
255 | items[i] = geo_item
256 |
257 | return items
258 |
259 |
260 | def get_table_rows(self, table, schema=None):
261 | c = self.con.cursor()
262 | self._exec_sql(c, "SELECT COUNT(*) FROM %s" % self._table_name(schema, table))
263 | return c.fetchone()[0]
264 |
265 |
266 | def get_table_fields(self, table, schema=None):
267 | """ return list of columns in table """
268 | c = self.con.cursor()
269 | schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else ""
270 | sql = """SELECT a.attnum AS ordinal_position,
271 | a.attname AS column_name,
272 | t.typname AS data_type,
273 | a.attlen AS char_max_len,
274 | a.atttypmod AS modifier,
275 | a.attnotnull AS notnull,
276 | a.atthasdef AS hasdefault,
277 | adef.adsrc AS default_value
278 | FROM pg_class c
279 | JOIN pg_attribute a ON a.attrelid = c.oid
280 | JOIN pg_type t ON a.atttypid = t.oid
281 | JOIN pg_namespace nsp ON c.relnamespace = nsp.oid
282 | LEFT JOIN pg_attrdef adef ON adef.adrelid = a.attrelid AND adef.adnum = a.attnum
283 | WHERE
284 | c.relname = '%s' %s AND
285 | a.attnum > 0
286 | ORDER BY a.attnum""" % (self._quote_str(table), schema_where)
287 |
288 | self._exec_sql(c, sql)
289 | attrs = []
290 | for row in c.fetchall():
291 | attrs.append(TableAttribute(row))
292 | return attrs
293 |
294 |
295 | def get_table_indexes(self, table, schema=None):
296 | """ get info about table's indexes. ignore primary key and unique constraint index, they get listed in constaints """
297 | c = self.con.cursor()
298 |
299 | schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else ""
300 | sql = """SELECT relname, indkey FROM pg_class, pg_index
301 | WHERE pg_class.oid = pg_index.indexrelid AND pg_class.oid IN (
302 | SELECT indexrelid FROM pg_index, pg_class
303 | JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid
304 | WHERE pg_class.relname='%s' %s AND pg_class.oid=pg_index.indrelid
305 | AND indisprimary != 't' )""" % (self._quote_str(table), schema_where) # AND indisunique != 't'
306 | self._exec_sql(c, sql)
307 | indexes = []
308 | for row in c.fetchall():
309 | indexes.append(TableIndex(row))
310 | return indexes
311 |
312 |
313 | def get_table_unique_indexes(self, table, schema=None):
314 | """ get all the unique indexes """
315 | schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else ""
316 | sql = """SELECT relname, indkey
317 | FROM pg_index JOIN pg_class ON pg_index.indrelid=pg_class.oid
318 | JOIN pg_namespace nsp ON pg_class.relnamespace = nsp.oid
319 | WHERE pg_class.relname='%s' %s
320 | AND indisprimary != 't' AND indisunique = 't'""" % (self._quote_str(table), schema_where)
321 | c = self.con.cursor()
322 | self._exec_sql(c, sql)
323 | uniqueIndexes = []
324 | for row in c.fetchall():
325 | uniqueIndexes.append(TableIndex(row))
326 | return uniqueIndexes
327 |
328 |
329 | def get_table_constraints(self, table, schema=None):
330 | c = self.con.cursor()
331 |
332 | schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else ""
333 | sql = """SELECT c.conname, c.contype, c.condeferrable, c.condeferred, array_to_string(c.conkey, ' '), c.consrc,
334 | t2.relname, c.confupdtype, c.confdeltype, c.confmatchtype, array_to_string(c.confkey, ' ') FROM pg_constraint c
335 | LEFT JOIN pg_class t ON c.conrelid = t.oid
336 | LEFT JOIN pg_class t2 ON c.confrelid = t2.oid
337 | JOIN pg_namespace nsp ON t.relnamespace = nsp.oid
338 | WHERE t.relname = '%s' %s """ % (self._quote_str(table), schema_where)
339 |
340 | self._exec_sql(c, sql)
341 |
342 | constrs = []
343 | for row in c.fetchall():
344 | constrs.append(TableConstraint(row))
345 | return constrs
346 |
347 |
348 | def get_table_triggers(self, table, schema=None):
349 | c = self.con.cursor()
350 |
351 | schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else ""
352 | sql = """ SELECT tgname, proname, tgtype, tgenabled FROM pg_trigger trig
353 | LEFT JOIN pg_class t ON trig.tgrelid = t.oid
354 | LEFT JOIN pg_proc p ON trig.tgfoid = p.oid
355 | JOIN pg_namespace nsp ON t.relnamespace = nsp.oid
356 | WHERE t.relname ='%s' %s """ % (self._quote_str(table), schema_where)
357 |
358 | self._exec_sql(c, sql)
359 |
360 | triggers = []
361 | for row in c.fetchall():
362 | triggers.append(TableTrigger(row))
363 | return triggers
364 |
365 |
366 | def get_table_rules(self, table, schema=None):
367 | c = self.con.cursor()
368 |
369 | schema_where = " AND schemaname='%s' " % self._quote_str(schema) if schema is not None else ""
370 | sql = """ SELECT rulename, definition FROM pg_rules
371 | WHERE tablename='%s' %s """ % (self._quote_str(table), schema_where)
372 |
373 | self._exec_sql(c, sql)
374 |
375 | rules = []
376 | for row in c.fetchall():
377 | rules.append(TableRule(row))
378 |
379 | return rules
380 |
381 | def get_table_estimated_extent(self, geom, table, schema=None):
382 | """ find out estimated extent (from the statistics) """
383 | c = self.con.cursor()
384 |
385 | extent = "estimated_extent('%s','%s','%s')" % (self._quote_str(schema), self._quote_str(table), self._quote_str(geom))
386 | sql = """ SELECT xmin(%(ext)s), ymin(%(ext)s), xmax(%(ext)s), ymax(%(ext)s) """ % { 'ext' : extent }
387 | self._exec_sql(c, sql)
388 |
389 | row = c.fetchone()
390 | return row
391 |
392 | def get_view_definition(self, view, schema=None):
393 | """ returns definition of the view """
394 | schema_where = " AND nspname='%s' " % self._quote_str(schema) if schema is not None else ""
395 | sql = """SELECT pg_get_viewdef(c.oid) FROM pg_class c
396 | JOIN pg_namespace nsp ON c.relnamespace = nsp.oid
397 | WHERE relname='%s' %s AND relkind='v'""" % (self._quote_str(view), schema_where)
398 | c = self.con.cursor()
399 | self._exec_sql(c, sql)
400 | return c.fetchone()[0]
401 |
402 | """
403 | def list_tables(self):
404 | c = self.con.cursor()
405 | c.execute("SELECT relname FROM pg_class WHERE relname !~ '^(pg_|sql_)' AND relkind = 'r'")
406 | return c.fetchall()
407 | """
408 |
409 | def add_geometry_column(self, table, geom_type, schema=None, geom_column='the_geom', srid=-1, dim=2):
410 |
411 | # use schema if explicitly specified
412 | if schema:
413 | schema_part = "'%s', " % self._quote_str(schema)
414 | else:
415 | schema_part = ""
416 | sql = "SELECT AddGeometryColumn(%s'%s', '%s', %d, '%s', %d)" % (schema_part, self._quote_str(table), self._quote_str(geom_column), srid, self._quote_str(geom_type), dim)
417 | self._exec_sql_and_commit(sql)
418 |
419 | def delete_geometry_column(self, table, geom_column, schema=None):
420 | """ use postgis function to delete geometry column correctly """
421 | if schema:
422 | schema_part = "'%s', " % self._quote_str(schema)
423 | else:
424 | schema_part = ""
425 | sql = "SELECT DropGeometryColumn(%s'%s', '%s')" % (schema_part, self._quote_str(table), self._quote_str(geom_column))
426 | self._exec_sql_and_commit(sql)
427 |
428 | def delete_geometry_table(self, table, schema=None):
429 | """ delete table with one or more geometries using postgis function """
430 | if schema:
431 | schema_part = "'%s', " % self._quote_str(schema)
432 | else:
433 | schema_part = ""
434 | sql = "SELECT DropGeometryTable(%s'%s')" % (schema_part, self._quote_str(table))
435 | self._exec_sql_and_commit(sql)
436 |
437 | def create_table(self, table, fields, pkey=None, schema=None):
438 | """ create ordinary table
439 | 'fields' is array containing instances of TableField
440 | 'pkey' contains name of column to be used as primary key
441 | """
442 |
443 | if len(fields) == 0:
444 | return False
445 |
446 | table_name = self._table_name(schema, table)
447 |
448 | sql = "CREATE TABLE %s (%s" % (table_name, fields[0].field_def(self))
449 | for field in fields[1:]:
450 | sql += ", %s" % field.field_def(self)
451 | if pkey:
452 | sql += ", PRIMARY KEY (%s)" % self._quote(pkey)
453 | sql += ")"
454 | self._exec_sql_and_commit(sql)
455 | return True
456 |
457 | def delete_table(self, table, schema=None):
458 | """ delete table from the database """
459 | table_name = self._table_name(schema, table)
460 | sql = "DROP TABLE %s" % table_name
461 | self._exec_sql_and_commit(sql)
462 |
463 | def empty_table(self, table, schema=None):
464 | """ delete all rows from table """
465 | table_name = self._table_name(schema, table)
466 | sql = "TRUNCATE %s" % table_name
467 | self._exec_sql_and_commit(sql)
468 |
469 | def rename_table(self, table, new_table, schema=None):
470 | """ rename a table in database """
471 | table_name = self._table_name(schema, table)
472 | sql = "ALTER TABLE %s RENAME TO %s" % (table_name, self._quote(new_table))
473 | self._exec_sql_and_commit(sql)
474 |
475 | # update geometry_columns if postgis is enabled
476 | if self.has_postgis and self.has_geometry_columns and self.has_geometry_columns_access:
477 | sql = "UPDATE geometry_columns SET f_table_name='%s' WHERE f_table_name='%s'" % (self._quote_str(new_table), self._quote_str(table))
478 | if schema is not None:
479 | sql += " AND f_table_schema='%s'" % self._quote_str(schema)
480 | self._exec_sql_and_commit(sql)
481 |
482 | def create_view(self, name, query, schema=None):
483 | view_name = self._table_name(schema, name)
484 | sql = "CREATE VIEW %s AS %s" % (view_name, query)
485 | self._exec_sql_and_commit(sql)
486 |
487 | def delete_view(self, name, schema=None):
488 | view_name = self._table_name(schema, name)
489 | sql = "DROP VIEW %s" % view_name
490 | self._exec_sql_and_commit(sql)
491 |
492 | def rename_view(self, name, new_name, schema=None):
493 | """ rename view in database """
494 | self.rename_table(name, new_name, schema)
495 |
496 | def create_schema(self, schema):
497 | """ create a new empty schema in database """
498 | sql = "CREATE SCHEMA %s" % self._quote(schema)
499 | self._exec_sql_and_commit(sql)
500 |
501 | def delete_schema(self, schema):
502 | """ drop (empty) schema from database """
503 | sql = "DROP SCHEMA %s" % self._quote(schema)
504 | self._exec_sql_and_commit(sql)
505 |
506 | def rename_schema(self, schema, new_schema):
507 | """ rename a schema in database """
508 | sql = "ALTER SCHEMA %s RENAME TO %s" % (self._quote(schema), self._quote(new_schema))
509 | self._exec_sql_and_commit(sql)
510 |
511 | # update geometry_columns if postgis is enabled
512 | if self.has_postgis:
513 | sql = "UPDATE geometry_columns SET f_table_schema='%s' WHERE f_table_schema='%s'" % (self._quote_str(new_schema), self._quote_str(schema))
514 | self._exec_sql_and_commit(sql)
515 |
516 | def table_add_column(self, table, field, schema=None):
517 | """ add a column to table (passed as TableField instance) """
518 | table_name = self._table_name(schema, table)
519 | sql = "ALTER TABLE %s ADD %s" % (table_name, field.field_def(self))
520 | self._exec_sql_and_commit(sql)
521 |
522 | def table_delete_column(self, table, field, schema=None):
523 | """ delete column from a table """
524 | table_name = self._table_name(schema, table)
525 | sql = "ALTER TABLE %s DROP %s" % (table_name, self._quote(field))
526 | self._exec_sql_and_commit(sql)
527 |
528 | def table_column_rename(self, table, name, new_name, schema=None):
529 | """ rename column in a table """
530 | table_name = self._table_name(schema, table)
531 | sql = "ALTER TABLE %s RENAME %s TO %s" % (table_name, self._quote(name), self._quote(new_name))
532 | self._exec_sql_and_commit(sql)
533 |
534 | # update geometry_columns if postgis is enabled
535 | if self.has_postgis:
536 | sql = "UPDATE geometry_columns SET f_geometry_column='%s' WHERE f_geometry_column='%s' AND f_table_name='%s'" % (self._quote_str(new_name), self._quote_str(name), self._quote_str(table))
537 | if schema is not None:
538 | sql += " AND f_table_schema='%s'" % self._quote(schema)
539 | self._exec_sql_and_commit(sql)
540 |
541 | def table_column_set_type(self, table, column, data_type, schema=None):
542 | """ change column type """
543 | table_name = self._table_name(schema, table)
544 | sql = "ALTER TABLE %s ALTER %s TYPE %s" % (table_name, self._quote(column), data_type)
545 | self._exec_sql_and_commit(sql)
546 |
547 | def table_column_set_default(self, table, column, default, schema=None):
548 | """ change column's default value. If default=None drop default value """
549 | table_name = self._table_name(schema, table)
550 | if default:
551 | sql = "ALTER TABLE %s ALTER %s SET DEFAULT %s" % (table_name, self._quote(column), default)
552 | else:
553 | sql = "ALTER TABLE %s ALTER %s DROP DEFAULT" % (table_name, self._quote(column))
554 | self._exec_sql_and_commit(sql)
555 |
556 | def table_column_set_null(self, table, column, is_null, schema=None):
557 | """ change whether column can contain null values """
558 | table_name = self._table_name(schema, table)
559 | sql = "ALTER TABLE %s ALTER %s " % (table_name, self._quote(column))
560 | if is_null:
561 | sql += "DROP NOT NULL"
562 | else:
563 | sql += "SET NOT NULL"
564 | self._exec_sql_and_commit(sql)
565 |
566 | def table_add_primary_key(self, table, column, schema=None):
567 | """ add a primery key (with one column) to a table """
568 | table_name = self._table_name(schema, table)
569 | sql = "ALTER TABLE %s ADD PRIMARY KEY (%s)" % (table_name, self._quote(column))
570 | self._exec_sql_and_commit(sql)
571 |
572 | def table_add_unique_constraint(self, table, column, schema=None):
573 | """ add a unique constraint to a table """
574 | table_name = self._table_name(schema, table)
575 | sql = "ALTER TABLE %s ADD UNIQUE (%s)" % (table_name, self._quote(column))
576 | self._exec_sql_and_commit(sql)
577 |
578 | def table_delete_constraint(self, table, constraint, schema=None):
579 | """ delete constraint in a table """
580 | table_name = self._table_name(schema, table)
581 | sql = "ALTER TABLE %s DROP CONSTRAINT %s" % (table_name, self._quote(constraint))
582 | self._exec_sql_and_commit(sql)
583 |
584 | def table_move_to_schema(self, table, new_schema, schema=None):
585 | if new_schema == schema:
586 | return
587 | table_name = self._table_name(schema, table)
588 | sql = "ALTER TABLE %s SET SCHEMA %s" % (table_name, self._quote(new_schema))
589 | self._exec_sql_and_commit(sql)
590 |
591 | # update geometry_columns if postgis is enabled
592 | if self.has_postgis:
593 | sql = "UPDATE geometry_columns SET f_table_schema='%s' WHERE f_table_name='%s'" % (self._quote_str(new_schema), self._quote_str(table))
594 | if schema is not None:
595 | sql += " AND f_table_schema='%s'" % self._quote_str(schema)
596 | self._exec_sql_and_commit(sql)
597 |
598 | def table_apply_function(self, schema, table, res_column, fct, param):
599 | """ apply a function to a column and save the result in other column """
600 | table = self._table_name(schema, table)
601 | sql = "UPDATE %s SET %s = %s(%s)" % (table, self._quote(res_column), fct, self._quote(param))
602 | self._exec_sql_and_commit(sql)
603 |
604 | def table_enable_triggers(self, table, schema, enable=True):
605 | """ enable or disable all triggers on table """
606 | table = self._table_name(schema, table)
607 | sql = "ALTER TABLE %s %s TRIGGER ALL" % (table, "ENABLE" if enable else "DISABLE")
608 | self._exec_sql_and_commit(sql)
609 |
610 | def table_enable_trigger(self, table, schema, trigger, enable=True):
611 | """ enable or disable one trigger on table """
612 | table = self._table_name(schema, table)
613 | sql = "ALTER TABLE %s %s TRIGGER %s" % (table, "ENABLE" if enable else "DISABLE", self._quote(trigger))
614 | self._exec_sql_and_commit(sql)
615 |
616 | def table_delete_trigger(self, table, schema, trigger):
617 | """ delete trigger on table """
618 | table = self._table_name(schema, table)
619 | sql = "DROP TRIGGER %s ON %s" % (self._quote(trigger), table)
620 | self._exec_sql_and_commit(sql)
621 |
622 | def table_delete_rule(self, table, schema, rule):
623 | """ delete rule on table """
624 | table = self._table_name(schema, table)
625 | sql = "DROP RULE %s ON %s" % (self._quote(rule), table)
626 | self._exec_sql_and_commit(sql)
627 |
628 | def create_index(self, table, name, column, schema=None):
629 | """ create index on one column using default options """
630 | table_name = self._table_name(schema, table)
631 | idx_name = self._quote(name)
632 | sql = "CREATE INDEX %s ON %s (%s)" % (idx_name, table_name, self._quote(column))
633 | self._exec_sql_and_commit(sql)
634 |
635 | def create_spatial_index(self, table, schema=None, geom_column='the_geom'):
636 | table_name = self._table_name(schema, table)
637 | idx_name = self._quote("sidx_"+table)
638 | sql = "CREATE INDEX %s ON %s USING GIST(%s GIST_GEOMETRY_OPS)" % (idx_name, table_name, self._quote(geom_column))
639 | self._exec_sql_and_commit(sql)
640 |
641 | def delete_index(self, name, schema=None):
642 | index_name = self._table_name(schema, name)
643 | sql = "DROP INDEX %s" % index_name
644 | self._exec_sql_and_commit(sql)
645 |
646 | def get_database_privileges(self):
647 | """ db privileges: (can create schemas, can create temp. tables) """
648 | sql = "SELECT has_database_privilege('%(d)s', 'CREATE'), has_database_privilege('%(d)s', 'TEMP')" % { 'd' : self._quote_str(self.dbname) }
649 | c = self.con.cursor()
650 | self._exec_sql(c, sql)
651 | return c.fetchone()
652 |
653 | def get_schema_privileges(self, schema):
654 | """ schema privileges: (can create new objects, can access objects in schema) """
655 | sql = "SELECT has_schema_privilege('%(s)s', 'CREATE'), has_schema_privilege('%(s)s', 'USAGE')" % { 's' : self._quote_str(schema) }
656 | c = self.con.cursor()
657 | self._exec_sql(c, sql)
658 | return c.fetchone()
659 |
660 | def get_table_privileges(self, table, schema=None):
661 | """ table privileges: (select, insert, update, delete) """
662 | t = self._table_name(schema, table)
663 | sql = """SELECT has_table_privilege('%(t)s', 'SELECT'), has_table_privilege('%(t)s', 'INSERT'),
664 | has_table_privilege('%(t)s', 'UPDATE'), has_table_privilege('%(t)s', 'DELETE')""" % { 't': self._quote_str(t) }
665 | c = self.con.cursor()
666 | self._exec_sql(c, sql)
667 | return c.fetchone()
668 |
669 | def vacuum_analyze(self, table, schema=None):
670 | """ run vacuum analyze on a table """
671 | t = self._table_name(schema, table)
672 | # vacuum analyze must be run outside transaction block - we have to change isolation level
673 | self.con.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_AUTOCOMMIT)
674 | c = self.con.cursor()
675 | self._exec_sql(c, "VACUUM ANALYZE %s" % t)
676 | self.con.set_isolation_level(psycopg2.extensions.ISOLATION_LEVEL_READ_COMMITTED)
677 |
678 | def get_srid_from_geom(self, geomFieldName, query):
679 | if not self.has_postgis:
680 | return "Unknown"
681 |
682 | try:
683 | c = self.con.cursor()
684 | self._exec_sql(c, "select getsrid(foo.%s) from (%s) as foo limit 1" % (geomFieldName, query))
685 | sr = c.fetchone()
686 | if sr is None:
687 | return "-1"
688 | srtext = sr[0]
689 | return srtext
690 | except DbError, e:
691 | return "Unknown"
692 |
693 | def sr_info_for_srid(self, srid):
694 | if not self.has_postgis:
695 | return "Unknown"
696 |
697 | try:
698 | c = self.con.cursor()
699 | self._exec_sql(c, "SELECT srtext FROM spatial_ref_sys WHERE srid = '%d'" % srid)
700 | sr = c.fetchone()
701 | if sr is None:
702 | return "Unknown"
703 | srtext = sr[0]
704 | # try to extract just SR name (should be qouted in double quotes)
705 | x = re.search('"([^"]+)"', srtext)
706 | if x is not None:
707 | srtext = x.group()
708 | return srtext
709 | except DbError, e:
710 | return "Unknown"
711 |
712 | def insert_table_row(self, table, values, schema=None, cursor=None):
713 | """ insert a row with specified values to a table.
714 | if a cursor is specified, it doesn't commit (expecting that there will be more inserts)
715 | otherwise it commits immediately """
716 | t = self._table_name(schema, table)
717 | sql = ""
718 | for value in values:
719 | # TODO: quote values?
720 | if sql: sql += ", "
721 | sql += value
722 | sql = "INSERT INTO %s VALUES (%s)" % (t, sql)
723 | if cursor:
724 | self._exec_sql(cursor, sql)
725 | else:
726 | self._exec_sql_and_commit(sql)
727 |
728 |
729 | def table_add_function_trigger(self, schema, table, resColumn, fct, geomColumn):
730 | """ add a trigger on insert and update that recalculates the value from geometry column """
731 |
732 | trig_f_name = "%s_calc_%s" % (table, fct)
733 | trig_name = "calc_%s" % fct
734 | ctx = { 'fname' : trig_f_name, 'tname' : trig_name,
735 | 'res' : resColumn, 'geom' : geomColumn,
736 | 'f' : fct, 'table' : self._table_name(schema, table) }
737 | sql = """
738 | CREATE OR REPLACE FUNCTION %(fname)s() RETURNS TRIGGER AS
739 | $$
740 | BEGIN
741 | IF (TG_OP = 'INSERT') THEN
742 | NEW.%(res)s := %(f)s(NEW.%(geom)s);
743 | ELSIF (TG_OP = 'UPDATE') THEN
744 | IF NOT (NEW.%(geom)s ~= OLD.%(geom)s) THEN
745 | NEW.%(res)s := %(f)s(NEW.%(geom)s);
746 | END IF;
747 | END IF;
748 | RETURN NEW;
749 | END;
750 | $$
751 | LANGUAGE 'plpgsql';
752 |
753 | CREATE TRIGGER %(tname)s BEFORE INSERT OR UPDATE ON %(table)s FOR EACH ROW
754 | EXECUTE PROCEDURE %(fname)s();
755 | """ % ctx
756 |
757 | self._exec_sql_and_commit(sql)
758 |
759 |
760 | def get_named_cursor(self, table=None):
761 | """ return an unique named cursor, optionally including a table name """
762 | self.last_cursor_id += 1
763 | if table is not None:
764 | table2 = re.sub(r'\W', '_', table.encode('ascii','replace')) # all non-alphanum characters to underscore
765 | cur_name = "cursor_%d_table_%s" % (self.last_cursor_id, table2)
766 | else:
767 | cur_name = "cursor_%d" % self.last_cursor_id
768 | #cur_name = ("\"db_table_"+self.table+"\"").replace(' ', '_')
769 | #cur_name = cur_name.encode('ascii','replace').replace('?', '_')
770 | return self.con.cursor(cur_name)
771 |
772 | def _exec_sql(self, cursor, sql):
773 | try:
774 | cursor.execute(sql)
775 | except psycopg2.Error, e:
776 | # do the rollback to avoid a "current transaction aborted, commands ignored" errors
777 | self.con.rollback()
778 | raise DbError(e)
779 |
780 | def _exec_sql_and_commit(self, sql):
781 | """ tries to execute and commit some action, on error it rolls back the change """
782 | #try:
783 | c = self.con.cursor()
784 | self._exec_sql(c, sql)
785 | self.con.commit()
786 | #except DbError, e:
787 | # self.con.rollback()
788 | # raise
789 |
790 | def _quote(self, identifier):
791 | identifier = unicode(identifier) # make sure it's python unicode string
792 | return u'"%s"' % identifier.replace('"', '""')
793 |
794 | def _quote_str(self, txt):
795 | """ make the string safe - replace ' with '' """
796 | txt = unicode(txt) # make sure it's python unicode string
797 | return txt.replace("'", "''")
798 |
799 | def _table_name(self, schema, table):
800 | if not schema:
801 | return self._quote(table)
802 | else:
803 | return u"%s.%s" % (self._quote(schema), self._quote(table))
804 |
805 |
806 | # for debugging / testing
807 | if __name__ == '__main__':
808 |
809 | db = GeoDB(host='localhost',dbname='gis',user='gisak',passwd='g')
810 |
811 | print db.list_schemas()
812 | print '=========='
813 |
814 | for row in db.list_geotables():
815 | print row
816 |
817 | print '=========='
818 |
819 | for row in db.get_table_indexes('trencin'):
820 | print row
821 |
822 | print '=========='
823 |
824 | for row in db.get_table_constraints('trencin'):
825 | print row
826 |
827 | print '=========='
828 |
829 | print db.get_table_rows('trencin')
830 |
831 | #for fld in db.get_table_metadata('trencin'):
832 | # print fld
833 |
834 | #try:
835 | # db.create_table('trrrr', [('id','serial'), ('test','text')])
836 | #except DbError, e:
837 | # print e.message, e.query
838 |
839 |
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/postgislayer.py:
--------------------------------------------------------------------------------
1 | """
2 | /***************************************************************************
3 | Fast SQL Layer
4 | A QGIS plugin
5 | Just type the query to add the layer
6 | -------------------
7 | begin : 2011-05-12
8 | copyright : (C) 2011 by Pablo Torres Carreira
9 | email : pablotcarreira@hotmail.com
10 | ***************************************************************************/
11 |
12 | /***************************************************************************
13 | * *
14 | * This program is free software; you can redistribute it and/or modify *
15 | * it under the terms of the GNU General Public License as published by *
16 | * the Free Software Foundation; either version 2 of the License, or *
17 | * (at your option) any later version. *
18 | * *
19 | ***************************************************************************/
20 | """
21 | # Import the PyQt and QGIS libraries
22 | from PyQt4 import uic
23 | from PyQt4.QtCore import *
24 | from PyQt4.QtGui import *
25 | from qgis.core import *
26 |
27 | import highlighter as hl
28 | import os, re
29 | import resources
30 |
31 | import postgis_utils
32 | # Initialize Qt resources from file resources.py
33 |
34 |
35 | class PostgisLayer:
36 | def __init__(self, iface, host, port, dbname, user, passwd):
37 | # Save reference to the QGIS interface
38 | self.iface = iface
39 | self.host = host
40 | self.port = port
41 | self.dbname = dbname
42 | self.user = user
43 | self.passwd = passwd
44 |
45 | def initGui(self):
46 | # Create action that will start plugin configuration
47 | self.action = QAction(QIcon(":/plugins/postgislayer/icon.png"), "Fast SQL Layer", self.iface.mainWindow())
48 | #Add toolbar button and menu item
49 | self.iface.addToolBarIcon(self.action)
50 |
51 |
52 | #load the form
53 | path = os.path.dirname(os.path.abspath(__file__))
54 | self.dock = uic.loadUi(os.path.join(path, "ui_postgislayer.ui"))
55 | self.iface.addDockWidget(Qt.BottomDockWidgetArea, self.dock)
56 |
57 |
58 | #connect the action to the run method
59 | QObject.connect(self.action, SIGNAL("triggered()"), self.show)
60 | QObject.connect(self.dock.buttonRun, SIGNAL('clicked()'), self.run)
61 |
62 | #populate the id and the_geom combos
63 | self.dock.uniqueCombo.addItem('id')
64 | self.dock.geomCombo.addItem('the_geom')
65 |
66 | #start the highlight engine
67 | self.higlight_text = hl.Highlighter(self.dock.textQuery.document(), "sql")
68 |
69 | def show(self):
70 | self.iface.addDockWidget(Qt.BottomDockWidgetArea, self.dock)
71 |
72 | def unload(self):
73 | # Remove the plugin menu item and icon
74 | self.iface.removeToolBarIcon(self.action)
75 |
76 |
77 | def run(self):
78 | try:
79 | import psycopg2
80 | except ImportError, e:
81 | QMessageBox.information(self.iface.mainWindow(), "Warning", "Couldn't import Python module 'psycopg2' for communication with PostgreSQL database. Without it you won't be able to run this tool. Please install it.")
82 | return
83 |
84 | uniqueFieldName = self.dock.uniqueCombo.currentText()
85 | geomFieldName = self.dock.geomCombo.currentText()
86 |
87 | try:
88 | db = postgis_utils.GeoDB( self.host, int(self.port), self.dbname, self.user, self.passwd )
89 | except postgis_utils.DbError, e:
90 | QMessageBox.critical(self.iface.mainWindow(), "error", "Couldn't connect to database:\n"+e.msg)
91 | return
92 |
93 | uri = QgsDataSourceURI()
94 | uri.setConnection(self.host, self.port, self.dbname, self.user, self.passwd)
95 |
96 | query = str(self.dock.textQuery.toPlainText()).lstrip().replace(";","")
97 |
98 | # Validate query
99 | if not re.match("^SELECT", query.upper() ):
100 | QMessageBox.critical(self.iface.mainWindow(), "error", "The query has to be a SELECT clause.")
101 | return
102 | try:
103 | db._exec_sql( db.con.cursor(), query )
104 | except postgis_utils.DbError, e:
105 | QMessageBox.critical(self.iface.mainWindow(), "error", str(e))
106 | return
107 |
108 | QApplication.setOverrideCursor(QCursor(Qt.WaitCursor))
109 |
110 | # Get srid
111 | try:
112 | srid = db.get_srid_from_geom( geomFieldName, query )
113 | except postgis_utils.DbError, e:
114 | QMessageBox.critical(self.iface.mainWindow(), "error", e.msg)
115 | return
116 |
117 | #lstrip() is needed to remove spaces in the first line.
118 | uri.setDataSource("", "(" + query + ")", geomFieldName, "", uniqueFieldName)
119 | vl = self.iface.addVectorLayer(uri.uri(), "QueryLayer", "postgres", srid)
120 | if not vl:
121 | QMessageBox.information(self.iface.mainWindow(), "Warning", "Couldn't load" + \
122 | "the layer. It doesn't seem to be a valid layer.")
123 | QApplication.restoreOverrideCursor()
124 |
125 |
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/readme:
--------------------------------------------------------------------------------
1 | This plugin is avaliable throug Qgis Contributed Repository
2 | http://pyqgis.org/repo/contributed
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/resources.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 |
3 | # Resource object code
4 | #
5 | # Created: qui 12. mai 16:13:42 2011
6 | # by: The Resource Compiler for PyQt (Qt v4.7.2)
7 | #
8 | # WARNING! All changes made in this file will be lost!
9 |
10 | from PyQt4 import QtCore
11 |
12 | qt_resource_data = "\
13 | \x00\x00\x04\x0a\
14 | \x89\
15 | \x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
16 | \x00\x00\x17\x00\x00\x00\x18\x08\x06\x00\x00\x00\x11\x7c\x66\x75\
17 | \x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
18 | \x06\x62\x4b\x47\x44\x00\xff\x00\xff\x00\xff\xa0\xbd\xa7\x93\x00\
19 | \x00\x00\x09\x70\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\
20 | \x00\x9a\x9c\x18\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xd9\x02\x15\
21 | \x16\x11\x2c\x9d\x48\x83\xbb\x00\x00\x03\x8a\x49\x44\x41\x54\x48\
22 | \xc7\xad\x95\x4b\x68\x5c\x55\x18\xc7\x7f\xe7\xdc\x7b\x67\xe6\xce\
23 | \x4c\x66\x26\x49\xd3\x24\x26\xa6\xc6\xf8\x40\x21\xa5\x04\xb3\x28\
24 | \xda\x98\x20\xa5\x0b\xad\x55\xa8\x2b\xc5\x50\x1f\xa0\x6e\x34\x2b\
25 | \x45\x30\x14\x02\xba\x52\x69\x15\x17\x66\x63\x45\x97\x95\xa0\xad\
26 | \x0b\xfb\xc0\x06\x25\xb6\x71\x61\x12\x41\x50\xdb\x2a\x21\xd1\xe2\
27 | \x24\xf3\x9e\xc9\xcc\xbd\xe7\x1c\x17\x35\x43\x1e\x33\x21\xb6\xfd\
28 | \x56\x87\xf3\x9d\xfb\xfb\x1e\xf7\xff\x9d\x23\x8c\x31\x43\x95\xf4\
29 | \x85\x1e\x3f\x3b\x35\xac\xfd\xcc\x43\xdc\xa4\x49\x3b\xfe\x9d\x1d\
30 | \xdb\x7b\x22\x90\x78\xf8\xb2\x28\xa7\xbe\x7d\xc1\x4b\x9d\x79\xdf\
31 | \x18\x15\xe5\x16\x99\x10\x56\xde\x69\xdc\x3f\x22\xfd\xec\xd4\xf0\
32 | \xad\x04\x03\x18\xa3\xa2\x7e\x76\x6a\x58\xde\x68\x2b\xb4\x36\xf8\
33 | \xbe\xc6\x18\x53\xdb\xef\xe7\xfa\xec\xed\x67\x63\x10\x42\x00\xf0\
34 | \xfb\xd5\x65\x2a\x15\x45\xc7\x6d\x0d\x00\xc4\xa2\xc1\xaa\x6f\x0d\
35 | \x3e\x6c\xab\xc2\x1c\x56\xa4\x77\x4b\xb0\xf2\x35\x15\x5f\x21\x85\
36 | \xe0\xc8\x6b\x5f\x92\x2d\x37\x33\x39\xf9\x03\x27\x8e\x1f\xa2\xf7\
37 | \xbe\x9d\x04\x1c\x0b\x37\xe4\xac\xff\xa6\x30\x87\xbd\xba\x00\x6a\
38 | \x06\x79\xe5\xf5\xaf\x89\xd9\x92\xc5\xcc\x0a\xd9\x7c\x19\xcf\xe9\
39 | \xe2\xe4\xa9\x2f\x78\x7c\xff\x01\x72\x85\x0a\x2b\x65\x1f\xa5\x4c\
40 | \xb5\xb2\x55\x16\x80\xbd\x31\xda\xda\x20\x1f\x7d\x3e\xcd\xc2\xfd\
41 | \x59\xa6\x93\x39\x92\xd1\x22\xea\x9b\x16\xce\x9d\x3f\xce\xe0\x83\
42 | \x03\x24\x82\x59\x3a\xdb\x7b\x88\xc7\x82\x68\x63\x58\xc9\xcc\x62\
43 | \x8c\x21\x18\xb0\x6a\xc3\x37\x06\x49\x16\xff\x24\x6b\xa5\x49\xbb\
44 | \x25\xbc\xa2\xa6\x21\xbb\x40\x7f\xdf\x00\x83\xbd\x01\x8e\x3c\xd5\
45 | \x45\xd7\x8e\x6b\x9c\x9c\x98\x25\x1a\xb6\xe8\xbe\x3d\xc2\xdd\x77\
46 | \x44\x48\xc4\x1c\x22\xe1\xeb\x58\x59\xaf\xcf\xd3\x33\x29\x2e\x34\
47 | \x2d\x91\x93\x3e\xbe\x34\x78\x01\xc5\xe2\x61\xc5\xae\x72\x8e\x70\
48 | \xc8\xc2\x0d\x5a\xbc\xf5\xee\x2f\x9c\xfa\x3e\x86\x69\x7a\x8e\xcf\
49 | \x26\xe6\xf9\x63\xa1\x44\xa1\xa4\xd0\xda\x6c\x0d\x2f\x15\x7c\xb4\
50 | \x67\x28\x59\x0a\xcf\xd6\x54\xe2\x06\x13\x87\x2b\x6f\x68\xa6\x27\
51 | \xaf\x31\x32\x36\xc7\xb2\x7f\x17\xef\x7d\x7c\x8c\x33\x67\xcf\x12\
52 | \x70\x24\x4a\x69\xd6\x6a\x46\xd6\xd3\x70\x72\xa9\x82\x67\x34\x45\
53 | \xad\x28\xdb\x1a\x15\x34\x98\xff\x46\xed\xef\x37\x0d\x99\xbf\x4a\
54 | \x3c\x30\x38\xc0\xc8\x4b\xaf\x92\x5a\x9c\xe2\xe0\x23\x6d\x74\xb4\
55 | \xba\x84\x5d\x0b\x29\x45\x7d\xb8\x94\x82\x96\xb6\x10\xf3\xc5\x12\
56 | \x2a\xef\x53\x11\x1a\x63\xad\x3f\x93\x19\x85\xf1\xb1\x77\x58\x5a\
57 | \xf8\x99\x97\x9f\xe9\xa6\x75\x47\x90\xc6\xb8\x43\xd8\xb5\xb6\xce\
58 | \xfc\xfa\xfd\x00\xfb\x3e\xf4\xc8\x05\x35\xba\x5e\xeb\x46\x21\xf9\
59 | \xcf\x0a\xa9\x8c\x87\xe3\x48\xdc\x90\xb5\x6e\x98\x6a\xaa\x65\xf2\
60 | \x52\x92\x43\x2f\x5e\xc2\x8c\x02\x1a\x10\xf5\x07\xac\xc3\x75\x70\
61 | \x83\x92\x80\xb3\xf9\xd0\x26\xf8\x8f\xb3\x29\xc6\x3e\xb8\x8c\x19\
62 | \x35\x75\x6b\x7b\x7e\x3c\xca\x45\x0c\x7e\x49\x31\xf4\x58\x3b\xf7\
63 | \xf6\x34\x90\x88\x39\x04\x1c\x59\x1f\xfe\xdb\xd5\x3c\x5f\x9d\x4b\
64 | \x32\xfd\x44\xb2\xba\xd7\xfa\xb6\x60\xcf\xde\x16\xdc\x90\x45\x4c\
65 | \x4a\x2a\x9e\x62\xfe\x4e\xc5\xc8\xc1\x4e\xda\x76\x86\xe8\xe9\x0a\
66 | \xe3\xd8\x92\x58\xd4\xc6\xb2\x44\x6d\x78\x2a\x53\xe1\xca\x7c\x99\
67 | \x63\x5d\xbf\x56\x9d\xbd\x9f\x44\x18\x7a\xba\x95\x27\x0f\xb4\xd3\
68 | \xdc\x18\xc0\xf3\x0d\x52\x40\xd8\xb5\xb0\xa4\x20\x14\xb2\x70\x6c\
69 | \x81\x63\xcb\xaa\x42\xd6\xfd\xb7\xf4\xec\xa3\x06\xa0\x50\x52\xd8\
70 | \x4e\x1b\x7e\x4a\xd3\x31\xf9\x29\xcf\xfe\xd4\x49\x7f\x5f\x13\xfb\
71 | \xfa\x9b\x71\x43\x92\x58\xd4\x21\x18\x90\xac\xde\xb0\x42\x50\x13\
72 | \x58\x33\xf3\x88\x6b\xa1\xfd\x65\x96\xf2\x79\xc6\x43\x7b\xd8\x75\
73 | \x38\xcc\x3d\xdd\xd1\xaa\xcf\x71\xe4\xff\x7f\x91\x56\x33\xaf\xea\
74 | \x37\xe7\xa1\x94\x21\x16\xb5\xd1\x06\x2c\x29\x36\xf5\x72\x9b\x96\
75 | \x95\xc0\xc4\xda\x9d\x78\x83\x43\x53\x22\x80\x65\x09\x1c\xfb\x86\
76 | \xc1\x00\xe7\x25\x70\x14\x48\x6f\x1e\x22\x51\xe3\x75\xd9\xb6\xa5\
77 | \x81\xa3\x32\xb1\xfb\xf4\x0c\x30\xb8\xb1\x82\x9b\xb0\x09\x60\x30\
78 | \xb1\xfb\xf4\xcc\xbf\xa0\xe9\x6e\xae\x5a\xdf\x4b\x81\x00\x00\x00\
79 | \x00\x49\x45\x4e\x44\xae\x42\x60\x82\
80 | "
81 |
82 | qt_resource_name = "\
83 | \x00\x07\
84 | \x07\x3b\xe0\xb3\
85 | \x00\x70\
86 | \x00\x6c\x00\x75\x00\x67\x00\x69\x00\x6e\x00\x73\
87 | \x00\x0c\
88 | \x0e\x8f\xda\x02\
89 | \x00\x70\
90 | \x00\x6f\x00\x73\x00\x74\x00\x67\x00\x69\x00\x73\x00\x6c\x00\x61\x00\x79\x00\x65\x00\x72\
91 | \x00\x08\
92 | \x0a\x61\x5a\xa7\
93 | \x00\x69\
94 | \x00\x63\x00\x6f\x00\x6e\x00\x2e\x00\x70\x00\x6e\x00\x67\
95 | "
96 |
97 | qt_resource_struct = "\
98 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
99 | \x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
100 | \x00\x00\x00\x14\x00\x02\x00\x00\x00\x01\x00\x00\x00\x03\
101 | \x00\x00\x00\x32\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
102 | "
103 |
104 | def qInitResources():
105 | QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
106 |
107 | def qCleanupResources():
108 | QtCore.qUnregisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)
109 |
110 | qInitResources()
111 |
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | icon.png
4 |
5 |
6 |
--------------------------------------------------------------------------------
/postgis_viewer/plugins/FastSQLlayer/ui_postgislayer.ui:
--------------------------------------------------------------------------------
1 |
2 |
3 | PostgisQuery
4 |
5 |
6 |
7 | 0
8 | 0
9 | 390
10 | 155
11 |
12 |
13 |
14 | Query
15 |
16 |
17 |
18 |
19 | 6
20 |
21 |
22 | 5
23 |
24 | -
25 |
26 |
27 |
28 | 0
29 | 0
30 |
31 |
32 |
33 |
34 | 30
35 | 30
36 |
37 |
38 |
39 |
40 | -
41 |
42 |
-
43 |
44 |
45 |
46 | 30
47 | 16777215
48 |
49 |
50 |
51 | Id:
52 |
53 |
54 |
55 | -
56 |
57 |
58 |
59 | 0
60 | 0
61 |
62 |
63 |
64 | true
65 |
66 |
67 |
68 | -
69 |
70 |
71 | Geometry field:
72 |
73 |
74 |
75 | -
76 |
77 |
78 |
79 | 0
80 | 0
81 |
82 |
83 |
84 | true
85 |
86 |
87 |
88 | -
89 |
90 |
91 | Run
92 |
93 |
94 | false
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
--------------------------------------------------------------------------------
/postgis_viewer/postgis_viewer.bat:
--------------------------------------------------------------------------------
1 | @echo off
2 | set OSGEO4W_ROOT=C:\OSGeo4W
3 | PATH=%OSGEO4W_ROOT%\bin;%PATH%
4 | for %%f in (%OSGEO4W_ROOT%\etc\ini\*.bat) do call %%f
5 | set PYTHONPATH=C:\OSGeo4W\apps\qgis\python
6 | set PATH=C:\OSGeo4W\apps\qgis\bin;%PATH%
7 |
8 | start /B python "C:/Archivos de programa/PostgreSQL/8.4/bin\postgis_viewer\postgis_viewer.py" %*
9 |
--------------------------------------------------------------------------------
/postgis_viewer/postgis_viewer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 | """
4 | Basic PostGIS viewer based on QGIS libs. (v.1.6.1)
5 | Usage: postgis_viewer.py
6 |
7 | Options:
8 | -h host
9 | -p port
10 | -U user
11 | -W password
12 | -d database
13 | -s schema
14 | -t table
15 |
16 | Prerequisities:
17 | Qt, QGIS, libqt4-sql-psql
18 |
19 | Using as PgAdmin plugin, copy 'postgis_viewer.py' file on PATH and put following
20 | to 'plugins.ini' (/usr/share/pgadmin3/plugins.ini on Debian):
21 |
22 | Title=View PostGIS layer
23 | Command=postgis_viewer.py -h $$HOSTNAME -p $$PORT -U $$USERNAME -W $$PASSWORD -d $$DATABASE -s $$SCHEMA -t $$OBJECTNAME
24 | Description=View PostGIS layer
25 | Platform=unix
26 | ServerType=postgresql
27 | Database=Yes
28 | SetPassword=Yes
29 |
30 | Authors:
31 | Copyright (c) 2010 by Ivan Mincik, ivan.mincik@gista.sk
32 | Copyright (c) 2011-2015 Germán Carrillo, geotux_tuxman@linuxmail.org
33 |
34 | License: GNU General Public License v2.0
35 | """
36 |
37 | import os, sys, math, imp, fileinput, re
38 | import getopt
39 | import getpass, pickle # import stuff for ipc
40 |
41 | try:
42 | from PyQt4.QtSql import QSqlDatabase, QSqlQuery
43 | from PyQt4.QtGui import ( QActionGroup, QAction, QMainWindow, QApplication, QMessageBox,
44 | QStatusBar, QFrame, QLabel, QDockWidget, QTreeWidget, QTreeWidgetItem,
45 | QPixmap, QIcon, QFont, QMenu, QColorDialog, QAbstractItemView, QTabWidget,
46 | QBitmap, QColor, QWidget )
47 | from PyQt4.QtCore import ( SIGNAL, Qt, QString, QSharedMemory, QIODevice, QPoint,
48 | QObject, QSize )
49 | from PyQt4.QtNetwork import QLocalServer, QLocalSocket
50 |
51 | from qgis.core import ( QgsApplication, QgsDataSourceURI, QgsVectorLayer,
52 | QgsRasterLayer, QgsMapLayerRegistry, QgsContrastEnhancement )
53 | from qgis.gui import QgsMapCanvas, QgsMapToolPan, QgsMapToolZoom, QgsMapCanvasLayer
54 |
55 | except ImportError:
56 | print >> sys.stderr, 'E: Qt or QGIS not installed.'
57 | print >> sys.stderr, 'E: Exiting ...'
58 | sys.exit(1)
59 |
60 | # Set the qgis_prefix and the imgs_dir according to the current os
61 | qgis_prefix = ""
62 | imgs_dir = ""
63 | if os.name == "nt": # Windows
64 | qgis_prefix = "C:/OSGeo4W/apps/qgis/"
65 | imgs_dir = "postgis_viewer/imgs/"
66 | else: # Linux
67 | qgis_prefix = "/usr"
68 | imgs_dir = "/usr/bin/postgis_viewer/imgs/"
69 |
70 | class SingletonApp(QApplication):
71 |
72 | timeout = 1000
73 |
74 | def __init__(self, argv, application_id=None):
75 | QApplication.__init__(self, argv)
76 |
77 | self.socket_filename = unicode(os.path.expanduser("~/.ipc_%s" % self.generate_ipc_id()) )
78 | self.shared_mem = QSharedMemory()
79 | self.shared_mem.setKey(self.socket_filename)
80 |
81 | if self.shared_mem.attach():
82 | self.is_running = True
83 | return
84 |
85 | self.is_running = False
86 | if not self.shared_mem.create(1):
87 | print >>sys.stderr, "Unable to create single instance"
88 | return
89 | # start local server
90 | self.server = QLocalServer(self)
91 | # connect signal for incoming connections
92 | self.connect(self.server, SIGNAL("newConnection()"), self.receive_message)
93 | # if socket file exists, delete it
94 | if os.path.exists(self.socket_filename):
95 | os.remove(self.socket_filename)
96 | # listen
97 | self.server.listen(self.socket_filename)
98 |
99 | def __del__(self):
100 | self.shared_mem.detach()
101 | if not self.is_running:
102 | if os.path.exists(self.socket_filename):
103 | os.remove(self.socket_filename)
104 |
105 |
106 | def generate_ipc_id(self, channel=None):
107 | if channel is None:
108 | channel = os.path.basename(sys.argv[0])
109 | return "%s_%s" % (channel, getpass.getuser())
110 |
111 | def send_message(self, message):
112 | if not self.is_running:
113 | raise Exception("Client cannot connect to IPC server. Not running.")
114 | socket = QLocalSocket(self)
115 | socket.connectToServer(self.socket_filename, QIODevice.WriteOnly)
116 | if not socket.waitForConnected(self.timeout):
117 | raise Exception(str(socket.errorString()))
118 | socket.write(pickle.dumps(message))
119 | if not socket.waitForBytesWritten(self.timeout):
120 | raise Exception(str(socket.errorString()))
121 | socket.disconnectFromServer()
122 |
123 | def receive_message(self):
124 | socket = self.server.nextPendingConnection()
125 | if not socket.waitForReadyRead(self.timeout):
126 | print >>sys.stderr, socket.errorString()
127 | return
128 | byte_array = socket.readAll()
129 | self.handle_new_message(pickle.loads(str(byte_array)))
130 |
131 | def handle_new_message(self, message):
132 | self.emit( SIGNAL("loadPgLayer"), message )
133 |
134 |
135 | class ViewerWnd( QMainWindow ):
136 | def __init__( self, app, dictOpts ):
137 | QMainWindow.__init__( self )
138 | self.setWindowTitle( "PostGIS Layer Viewer - v.1.6.1" )
139 | self.setTabPosition( Qt.BottomDockWidgetArea, QTabWidget.North )
140 |
141 | self.canvas = QgsMapCanvas()
142 | self.canvas.setCanvasColor( Qt.white )
143 | self.canvas.useImageToRender( True )
144 | self.canvas.enableAntiAliasing( True )
145 | self.setCentralWidget( self.canvas )
146 |
147 | actionZoomIn = QAction( QIcon( imgs_dir + "mActionZoomIn.png" ), QString( "Zoom in" ), self )
148 | actionZoomOut = QAction( QIcon( imgs_dir + "mActionZoomOut.png" ), QString( "Zoom out" ), self )
149 | actionPan = QAction( QIcon( imgs_dir + "mActionPan.png" ), QString( "Pan" ), self )
150 | actionZoomFullExtent = QAction( QIcon( imgs_dir + "mActionZoomFullExtent.png" ), QString( "Zoom full" ), self )
151 |
152 | actionZoomIn.setCheckable( True )
153 | actionZoomOut.setCheckable( True )
154 | actionPan.setCheckable( True )
155 |
156 | self.connect(actionZoomIn, SIGNAL( "triggered()" ), self.zoomIn )
157 | self.connect(actionZoomOut, SIGNAL( "triggered()" ), self.zoomOut )
158 | self.connect(actionPan, SIGNAL( "triggered()" ), self.pan )
159 | self.connect(actionZoomFullExtent, SIGNAL( "triggered()" ), self.zoomFullExtent )
160 |
161 | self.actionGroup = QActionGroup( self )
162 | self.actionGroup.addAction( actionPan )
163 | self.actionGroup.addAction( actionZoomIn )
164 | self.actionGroup.addAction( actionZoomOut )
165 |
166 | # Create the toolbar
167 | self.toolbar = self.addToolBar( "Map tools" )
168 | self.toolbar.addAction( actionPan )
169 | self.toolbar.addAction( actionZoomIn )
170 | self.toolbar.addAction( actionZoomOut )
171 | self.toolbar.addAction( actionZoomFullExtent )
172 |
173 | # Create the map tools
174 | self.toolPan = QgsMapToolPan( self.canvas )
175 | self.toolPan.setAction( actionPan )
176 | self.toolZoomIn = QgsMapToolZoom( self.canvas, False ) # false = in
177 | self.toolZoomIn.setAction( actionZoomIn )
178 | self.toolZoomOut = QgsMapToolZoom( self.canvas, True ) # true = out
179 | self.toolZoomOut.setAction( actionZoomOut )
180 |
181 | # Create the statusbar
182 | self.statusbar = QStatusBar( self )
183 | self.statusbar.setObjectName( "statusbar" )
184 | self.setStatusBar( self.statusbar )
185 |
186 | self.lblXY = QLabel()
187 | self.lblXY.setFrameStyle( QFrame.Box )
188 | self.lblXY.setMinimumWidth( 170 )
189 | self.lblXY.setAlignment( Qt.AlignCenter )
190 | self.statusbar.setSizeGripEnabled( False )
191 | self.statusbar.addPermanentWidget( self.lblXY, 0 )
192 |
193 | self.lblScale = QLabel()
194 | self.lblScale.setFrameStyle( QFrame.StyledPanel )
195 | self.lblScale.setMinimumWidth( 140 )
196 | self.statusbar.addPermanentWidget( self.lblScale, 0 )
197 |
198 | self.createLegendWidget() # Create the legend widget
199 |
200 | self.connect( app, SIGNAL( "loadPgLayer" ), self.loadLayer )
201 | self.connect( self.canvas, SIGNAL( "scaleChanged(double)" ),
202 | self.changeScale )
203 | self.connect( self.canvas, SIGNAL( "xyCoordinates(const QgsPoint&)" ),
204 | self.updateXY )
205 |
206 | self.pan() # Default
207 |
208 | self.plugins = Plugins( self, self.canvas, dictOpts['-h'], dictOpts['-p'], dictOpts['-d'], dictOpts['-U'], dictOpts['-W'] )
209 |
210 | self.createAboutWidget()
211 | self.layerSRID = '-1'
212 | self.loadLayer( dictOpts )
213 |
214 | def zoomIn( self ):
215 | self.canvas.setMapTool( self.toolZoomIn )
216 |
217 | def zoomOut( self ):
218 | self.canvas.setMapTool( self.toolZoomOut )
219 |
220 | def pan( self ):
221 | self.canvas.setMapTool( self.toolPan )
222 |
223 | def zoomFullExtent( self ):
224 | self.canvas.zoomToFullExtent()
225 |
226 | def about( self ):
227 | pass
228 |
229 | def createLegendWidget( self ):
230 | """ Create the map legend widget and associate it to the canvas """
231 | self.legend = Legend( self )
232 | self.legend.setCanvas( self.canvas )
233 | self.legend.setObjectName( "theMapLegend" )
234 |
235 | self.LegendDock = QDockWidget( "Layers", self )
236 | self.LegendDock.setObjectName( "legend" )
237 | self.LegendDock.setTitleBarWidget( QWidget() )
238 | self.LegendDock.setWidget( self.legend )
239 | self.LegendDock.setContentsMargins ( 0, 0, 0, 0 )
240 | self.addDockWidget( Qt.BottomDockWidgetArea, self.LegendDock )
241 |
242 | def createAboutWidget( self ):
243 | self.AboutDock = QDockWidget( "About", self )
244 | self.AboutDock.setObjectName( "about" )
245 | self.AboutDock.setTitleBarWidget( QWidget() )
246 | self.AboutDock.setContentsMargins( 0, 0, 0, 0 )
247 | self.tabifyDockWidget( self.LegendDock, self.AboutDock )
248 | self.LegendDock.raise_() # legendDock at the top
249 |
250 | from PyQt4.QtCore import QRect
251 | from PyQt4.QtGui import QSizePolicy, QGridLayout, QFont
252 | font = QFont()
253 | font.setFamily("Sans Serif")
254 | font.setPointSize(8.7)
255 | self.AboutWidget = QWidget()
256 | self.AboutWidget.setFont( font )
257 | self.AboutWidget.setObjectName("AboutWidget")
258 | self.AboutDock.setWidget( self.AboutWidget )
259 | self.labelAbout = QLabel( self.AboutWidget )
260 | self.labelAbout.setAlignment(Qt.AlignCenter)
261 | self.labelAbout.setWordWrap(True)
262 | self.gridLayout = QGridLayout(self.AboutWidget)
263 | self.gridLayout.setContentsMargins(0, 0, 0, 0)
264 | self.gridLayout.setObjectName("gridLayout")
265 | self.gridLayout.addWidget(self.labelAbout, 0, 1, 1, 1)
266 | self.labelAbout.setTextInteractionFlags(Qt.LinksAccessibleByMouse|Qt.LinksAccessibleByKeyboard|Qt.TextSelectableByKeyboard|Qt.TextSelectableByMouse)
267 | self.labelAbout.setOpenExternalLinks( True )
268 | self.labelAbout.setText("PostGIS Layer Viewer v.1.6.1 (2015.02.24)
" \
269 | "Copyright (c) 2010 Ivan Mincik,
ivan.mincik@gista.sk
" \
270 | u"Copyright (c) 2011-2015 Germán Carrillo,
geotux_tuxman@linuxmail.org
" \
271 | "Licensed under the terms of GNU GPL v.2.0
" \
272 | "Based on PyQGIS. Plugin Fast SQL Layer by Pablo T. Carreira." )
273 |
274 | def loadLayer( self, dictOpts ):
275 | print 'I: Loading the layer...'
276 | self.layerSRID = dictOpts[ 'srid' ] # To access the SRID when querying layer properties
277 |
278 | if not self.isActiveWindow():
279 | self.activateWindow()
280 | self.raise_()
281 |
282 | if dictOpts['type'] == 'vector':
283 | # QGIS connection
284 | uri = QgsDataSourceURI()
285 | uri.setConnection( dictOpts['-h'], dictOpts['-p'], dictOpts['-d'],
286 | dictOpts['-U'], dictOpts['-W'] )
287 | uri.setDataSource( dictOpts['-s'], dictOpts['-t'], dictOpts['-g'] )
288 | layer = QgsVectorLayer( uri.uri(), dictOpts['-s'] + '.' + dictOpts['-t'],
289 | "postgres" )
290 | elif dictOpts['type'] == 'raster':
291 | connString = "PG: dbname=%s host=%s user=%s password=%s port=%s mode=2 " \
292 | "schema=%s column=%s table=%s" % ( dictOpts['-d'], dictOpts['-h'],
293 | dictOpts['-U'], dictOpts['-W'], dictOpts['-p'], dictOpts['-s'],
294 | dictOpts['col'], dictOpts['-t'] )
295 | layer = QgsRasterLayer( connString, dictOpts['-s'] + '.' + dictOpts['-t'] )
296 |
297 | if layer.isValid():
298 | layer.setContrastEnhancement( QgsContrastEnhancement.StretchToMinimumMaximum )
299 |
300 | self.addLayer( layer, self.layerSRID )
301 |
302 | def addLayer( self, layer, srid='-1' ):
303 | if layer.isValid():
304 | # Only in case that srid != -1, read the layer SRS properties, otherwise don't since it will return 4326
305 | if srid != '-1':
306 | self.layerSRID = layer.crs().description() + ' (' + str( layer.crs().postgisSrid() ) + ')'
307 | else:
308 | self.layerSRID = 'Unknown SRS (-1)'
309 |
310 | if self.canvas.layerCount() == 0:
311 | self.canvas.setExtent( layer.extent() )
312 |
313 | if srid != '-1':
314 | print 'I: Map SRS (EPSG): %s' % self.layerSRID
315 | self.canvas.setMapUnits( layer.crs().mapUnits() )
316 | else:
317 | print 'I: Unknown Reference System'
318 | self.canvas.setMapUnits( 0 ) # 0: QGis.Meters
319 |
320 | return QgsMapLayerRegistry.instance().addMapLayer( layer )
321 | return False
322 |
323 | def activeLayer( self ):
324 | """ Returns the active layer in the layer list widget """
325 | return self.legend.activeLayer()
326 |
327 | def getLayerProperties( self, l ):
328 | """ Create a layer-properties string (l:layer)"""
329 | print 'I: Generating layer properties...'
330 | if l.type() == 0: # Vector
331 | wkbType = ["WKBUnknown","WKBPoint","WKBLineString","WKBPolygon",
332 | "WKBMultiPoint","WKBMultiLineString","WKBMultiPolygon",
333 | "WKBNoGeometry","WKBPoint25D","WKBLineString25D","WKBPolygon25D",
334 | "WKBMultiPoint25D","WKBMultiLineString25D","WKBMultiPolygon25D"]
335 | properties = "Source: %s\n" \
336 | "Geometry type: %s\n" \
337 | "Number of features: %s\n" \
338 | "Number of fields: %s\n" \
339 | "SRS (EPSG): %s\n" \
340 | "Extent: %s " \
341 | % ( l.source(), wkbType[l.wkbType()], l.featureCount(),
342 | l.dataProvider().fields().count(), self.layerSRID,
343 | l.extent().toString() )
344 | elif l.type() == 1: # Raster
345 | rType = [ "GrayOrUndefined (single band)", "Palette (single band)", "Multiband", "ColorLayer" ]
346 | properties = "Source: %s\n" \
347 | "Raster type: %s\n" \
348 | "Width-Height (pixels): %sx%s\n" \
349 | "Bands: %s\n" \
350 | "SRS (EPSG): %s\n" \
351 | "Extent: %s" \
352 | % ( l.source(), rType[l.rasterType()], l.width(), l.height(),
353 | l.bandCount(), self.layerSRID, l.extent().toString() )
354 |
355 | self.layerSRID = '-1' # Initialize the srid
356 | return properties
357 |
358 | def changeScale( self, scale ):
359 | self.lblScale.setText( "Scale 1:" + formatNumber( scale ) )
360 |
361 | def updateXY( self, p ):
362 | if self.canvas.mapUnits() == 2: # Degrees
363 | self.lblXY.setText( formatToDegrees( p.x() ) + " | " \
364 | + formatToDegrees( p.y() ) )
365 | else: # Unidad lineal
366 | self.lblXY.setText( formatNumber( p.x() ) + " | " \
367 | + formatNumber( p.y() ) + "" )
368 |
369 |
370 | # Class to expose qgis objects and functionalities to plugins
371 | class QgisInterface( QObject ):
372 | """ Class to expose qgis objects and functionalities to plugins """
373 | def __init__( self, myApp, canvas ):
374 | QObject.__init__( self )
375 | self.myApp = myApp
376 | self.canvas = canvas
377 | self.toolBarPlugins = None
378 |
379 | def addVectorLayer( self, vectorLayerPath, baseName, providerKey, srid=-1):
380 | layer = QgsVectorLayer( vectorLayerPath, baseName, providerKey )
381 | return self.myApp.addLayer( layer, srid )
382 |
383 | def zoomFull( self ):
384 | """ Zoom to the map full extent """
385 | self.canvas.zoomToFullExtent()
386 |
387 | def zoomToPrevious( self ):
388 | """ Zoom to previous view extent """
389 | self.canvas.zoomToPreviousExtent()
390 |
391 | def zoomToNext( self ):
392 | """ Zoom to next view extent """
393 | self.canvas.zoomToNextExtent()
394 |
395 | def activeLayer( self ):
396 | """ Get pointer to the active layer (layer selected in the legend) """
397 | if self.myApp.activeLayer():
398 | return self.myApp.activeLayer().layer()
399 | return None
400 |
401 | def addToolBarIcon( self, qAction ):
402 | """ Add an icon to the plugins toolbar """
403 | if not self.toolBarPlugins:
404 | self.toolBarPlugins = self.addToolBar( "Plugins" )
405 | self.toolBarPlugins.addAction( qAction )
406 |
407 | def removeToolBarIcon( self, qAction ):
408 | """ Remove an action (icon) from the plugin toolbar """
409 | if not self.toolBarPlugins:
410 | self.toolBarPlugins = self.addToolBar( "Plugins" )
411 | self.toolBarPlugins.removeAction( qAction )
412 |
413 | def addToolBar( self, name ):
414 | """ Add toolbar with specified name """
415 | toolBar = self.myApp.addToolBar( name )
416 | toolBar.setObjectName( name )
417 | return toolBar
418 |
419 | def mapCanvas( self ):
420 | """ Return a pointer to the map canvas """
421 | return self.canvas
422 |
423 | def mainWindow( self ):
424 | """ Return a pointer to the main window (instance of QgisApp in case of QGIS) """
425 | return self.myApp
426 |
427 | def addDockWidget( self, area, dockwidget ):
428 | """ Add a dock widget to the main window """
429 | dockwidget.setTitleBarWidget( QWidget() )
430 | self.myApp.tabifyDockWidget( self.myApp.LegendDock, dockwidget )
431 | self.myApp.LegendDock.raise_() # legendDock at the top
432 |
433 | # Class to manage plugins (Read and load the existing plugins)
434 | class Plugins():
435 | """ Class to manage plugins (Read and load existing plugins) """
436 | def __init__( self, myApp, canvas, host, port, dbname, user, passwd ):
437 | self.qgisInterface = QgisInterface( myApp, canvas )
438 | self.myApp = myApp
439 | self.plugins = []
440 | self.pluginsDirName = 'plugins'
441 | regExpString = '^def +classFactory\(.*iface.*(\):)$' # To find the classFactory line
442 |
443 | """ Validate that it is a plugins' folder and load them into the application """
444 | dir_plugins = os.path.join( os.path.dirname(__file__), self.pluginsDirName )
445 |
446 | if os.path.exists( dir_plugins ):
447 | for root, dirs, files in os.walk( dir_plugins ):
448 | bPlugIn = False
449 |
450 | if not dir_plugins == root:
451 | if '__init__.py' in files:
452 | for line in fileinput.input( os.path.join( root, '__init__.py' ) ):
453 | linea = line.strip()
454 | if re.match( regExpString, linea ):
455 | bPlugIn = True
456 | break
457 | fileinput.close()
458 |
459 | if bPlugIn:
460 | plugin_name = os.path.basename( root )
461 | f, filename, description = imp.find_module( plugin_name, [ dir_plugins ] )
462 |
463 | try:
464 | package = imp.load_module( plugin_name, f, filename, description )
465 | self.plugins.append( package.classFactory( self.qgisInterface, host, port, dbname, user, passwd ) )
466 | except Exception, e:
467 | print 'E: Plugin ' + plugin_name + ' could not be loaded. ERROR!:',e
468 | else:
469 | self.plugins[ -1 ].initGui()
470 | print 'I: Plugin ' + plugin_name + ' successfully loaded!'
471 | else:
472 | print "Plugins folder not found."
473 |
474 | # A couple of classes for the layer list widget and the layer properties
475 | class LegendItem( QTreeWidgetItem ):
476 | """ Provide a widget to show and manage the properties of one single layer """
477 | def __init__( self, parent, canvasLayer ):
478 | QTreeWidgetItem.__init__( self )
479 | self.legend = parent
480 | self.canvasLayer = canvasLayer
481 | self.canvasLayer.layer().setLayerName( self.legend.normalizeLayerName( unicode( self.canvasLayer.layer().name() ) ) )
482 | self.setText( 0, self.canvasLayer.layer().name() )
483 | self.isVect = ( self.canvasLayer.layer().type() == 0 ) # 0: Vector, 1: Raster
484 | self.layerId = self.canvasLayer.layer().id()
485 |
486 | if self.isVect:
487 | geom = self.canvasLayer.layer().dataProvider().geometryType()
488 |
489 | self.setCheckState( 0, Qt.Checked )
490 |
491 | if self.isVect:
492 | pm = QPixmap( 20, 20 )
493 | icon = QIcon()
494 | if geom == 1 or geom == 4 or geom == 8 or geom == 11: # Point
495 | icon.addPixmap( QPixmap( imgs_dir + "mIconPointLayer.png" ), QIcon.Normal, QIcon.On)
496 | elif geom == 2 or geom == 5 or geom == 9 or geom == 12: # Polyline
497 | icon.addPixmap( QPixmap( imgs_dir + "mIconLineLayer.png"), QIcon.Normal, QIcon.On)
498 | elif geom == 3 or geom == 6 or geom == 10 or geom == 13: # Polygon
499 | icon.addPixmap( QPixmap( imgs_dir + "mIconPolygonLayer.png"), QIcon.Normal, QIcon.On)
500 | else: # Not a valid WKT Geometry
501 | geom = self.canvasLayer.layer().geometryType() # QGis Geometry
502 | if geom == 0: # Point
503 | icon.addPixmap( QPixmap( imgs_dir + "mIconPointLayer.png" ), QIcon.Normal, QIcon.On)
504 | elif geom == 1: # Line
505 | icon.addPixmap( QPixmap( imgs_dir + "mIconLineLayer.png"), QIcon.Normal, QIcon.On)
506 | elif geom == 2: # Polygon
507 | icon.addPixmap( QPixmap( imgs_dir + "mIconPolygonLayer.png"), QIcon.Normal, QIcon.On)
508 | else:
509 | raise RuntimeError, 'Unknown geometry: ' + str( geom )
510 |
511 | else:
512 | qimg = self.canvasLayer.layer().previewAsImage( QSize(20,20) )
513 | icon = QIcon( QBitmap().fromImage( qimg ) )
514 |
515 | self.setIcon( 0, icon )
516 |
517 | #self.setToolTip( 0, self.canvasLayer.layer().publicSource() )
518 | layerFont = QFont()
519 | layerFont.setBold( True )
520 | self.setFont( 0, layerFont )
521 |
522 | # Display layer properties
523 | self.properties = self.legend.pyQGisApp.getLayerProperties( self.canvasLayer.layer() )
524 | self.child = QTreeWidgetItem( self )
525 | self.child.setFlags( Qt.NoItemFlags ) # Avoid the item to be selected
526 | self.displayLayerProperties()
527 |
528 | def displayLayerProperties( self ):
529 | """ It is required to build the QLabel widget every time it is set """
530 | propertiesFont = QFont()
531 | propertiesFont.setItalic( True )
532 | propertiesFont.setPointSize( 8 )
533 | propertiesFont.setStyleStrategy( QFont.PreferAntialias )
534 |
535 | label = QLabel( self.properties )
536 | label.setTextInteractionFlags( Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard )
537 | label.setFont( propertiesFont )
538 | self.legend.setItemWidget( self.child, 0, label )
539 |
540 | def nextSibling( self ):
541 | """ Return the next layer item """
542 | return self.legend.nextSibling( self )
543 |
544 | def storeAppearanceSettings( self ):
545 | """ Store the appearance of the layer item """
546 | self.__itemIsExpanded = self.isExpanded()
547 |
548 | def restoreAppearanceSettings( self ):
549 | """ Restore the appearance of the layer item """
550 | self.setExpanded( self.__itemIsExpanded )
551 | self.displayLayerProperties() # Generate the QLabel widget again
552 |
553 |
554 | class Legend( QTreeWidget ):
555 | """
556 | Provide a widget that manages map layers and their properties as tree items
557 | """
558 | def __init__( self, parent ):
559 | QTreeWidget.__init__( self, parent )
560 |
561 | self.pyQGisApp = parent
562 | self.canvas = None
563 | self.layers = self.getLayerSet()
564 |
565 | self.bMousePressedFlag = False
566 | self.itemBeingMoved = None
567 |
568 | # QTreeWidget properties
569 | self.setSortingEnabled( False )
570 | self.setDragEnabled( False )
571 | self.setAutoScroll( False )
572 | #self.setVerticalScrollMode( QAbstractItemView.ScrollPerPixel )
573 | self.setHeaderHidden( True )
574 | self.setRootIsDecorated( True )
575 | self.setContextMenuPolicy( Qt.CustomContextMenu )
576 |
577 | self.connect( self, SIGNAL( "customContextMenuRequested(QPoint)" ),
578 | self.showMenu )
579 | self.connect( QgsMapLayerRegistry.instance(), SIGNAL("layerWasAdded(QgsMapLayer *)"),
580 | self.addLayerToLegend )
581 | self.connect( QgsMapLayerRegistry.instance(), SIGNAL( "removedAll()" ),
582 | self.removeAll )
583 | self.connect( self, SIGNAL( "itemChanged(QTreeWidgetItem *,int)" ),
584 | self.updateLayerStatus )
585 | self.connect( self, SIGNAL( "currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)" ),
586 | self.currentItemChanged )
587 |
588 | def setCanvas( self, canvas ):
589 | """ Set the base canvas """
590 | self.canvas = canvas
591 |
592 | def showMenu( self, pos ):
593 | """ Show a context menu for the active layer in the legend """
594 | item = self.itemAt( pos )
595 | if item:
596 | if self.isLegendLayer( item ):
597 | self.setCurrentItem( item )
598 | self.menu = self.getMenu( item.isVect, item.canvasLayer )
599 | self.menu.popup( QPoint( self.mapToGlobal( pos ).x() + 5, self.mapToGlobal( pos ).y() ) )
600 |
601 | def getMenu( self, isVect, canvasLayer ):
602 | """ Create a context menu for a layer """
603 | menu = QMenu()
604 | menu.addAction( QIcon( imgs_dir + "mActionZoomToLayer.png" ), "&Zoom to layer extent", self.zoomToLayer )
605 | menu.addSeparator()
606 | if isVect :
607 | menu.addAction( QIcon( imgs_dir + "symbology.png" ), "&Symbology...", self.layerSymbology )
608 | menu.addSeparator()
609 | menu.addAction( QIcon( imgs_dir + "collapse.png" ), "&Collapse all", self.collapseAll )
610 | menu.addAction( QIcon( imgs_dir + "expand.png" ), "&Expand all", self.expandAll )
611 | menu.addSeparator()
612 | menu.addAction( QIcon( imgs_dir + "removeLayer.png" ), "&Remove layer", self.removeCurrentLayer )
613 | return menu
614 |
615 | def mousePressEvent(self, event):
616 | """ Mouse press event to manage the layers drag """
617 | if ( event.button() == Qt.LeftButton ):
618 | self.lastPressPos = event.pos()
619 | self.bMousePressedFlag = True
620 | QTreeWidget.mousePressEvent( self, event )
621 |
622 | def mouseMoveEvent(self, event):
623 | """ Mouse move event to manage the layers drag """
624 | if ( self.bMousePressedFlag ):
625 | # Set the flag back such that the else if(itemBeingMoved)
626 | # code part is passed during the next mouse moves
627 | self.bMousePressedFlag = False
628 |
629 | # Remember the item that has been pressed
630 | item = self.itemAt( self.lastPressPos )
631 | if ( item ):
632 | if ( self.isLegendLayer( item ) ):
633 | self.itemBeingMoved = item
634 | self.storeInitialPosition() # Store the initial layers order
635 | self.setCursor( Qt.SizeVerCursor )
636 | else:
637 | self.setCursor( Qt.ForbiddenCursor )
638 | elif ( self.itemBeingMoved ):
639 | p = QPoint( event.pos() )
640 | self.lastPressPos = p
641 |
642 | # Change the cursor
643 | item = self.itemAt( p )
644 | origin = self.itemBeingMoved
645 | dest = item
646 |
647 | if not item:
648 | self.setCursor( Qt.ForbiddenCursor )
649 |
650 | if ( item and ( item != self.itemBeingMoved ) ):
651 | if ( self.yCoordAboveCenter( dest, event.y() ) ): # Above center of the item
652 | if self.isLegendLayer( dest ): # The item is a layer
653 | if ( origin.nextSibling() != dest ):
654 | self.moveItem( dest, origin )
655 | self.setCurrentItem( origin )
656 | self.setCursor( Qt.SizeVerCursor )
657 | else:
658 | self.setCursor( Qt.ForbiddenCursor )
659 | else: # Below center of the item
660 | if self.isLegendLayer( dest ): # The item is a layer
661 | if ( self.itemBeingMoved != dest.nextSibling() ):
662 | self.moveItem( origin, dest )
663 | self.setCurrentItem( origin )
664 | self.setCursor( Qt.SizeVerCursor )
665 | else:
666 | self.setCursor( Qt.ForbiddenCursor )
667 |
668 | def mouseReleaseEvent( self, event ):
669 | """ Mouse release event to manage the layers drag """
670 | QTreeWidget.mouseReleaseEvent( self, event )
671 | self.setCursor( Qt.ArrowCursor )
672 | self.bMousePressedFlag = False
673 |
674 | if ( not self.itemBeingMoved ):
675 | #print "*** Legend drag: No itemBeingMoved ***"
676 | return
677 |
678 | dest = self.itemAt( event.pos() )
679 | origin = self.itemBeingMoved
680 | if ( ( not dest ) or ( not origin ) ): # Release out of the legend
681 | self.checkLayerOrderUpdate()
682 | return
683 |
684 | self.checkLayerOrderUpdate()
685 | self.itemBeingMoved = None
686 |
687 | def addLayerToLegend( self, canvasLayer ):
688 | """ Slot. Create and add a legend item based on a layer """
689 | legendLayer = LegendItem( self, QgsMapCanvasLayer( canvasLayer ) )
690 | self.addLayer( legendLayer )
691 |
692 | def addLayer( self, legendLayer ):
693 | """ Add a legend item to the legend widget """
694 | self.insertTopLevelItem ( 0, legendLayer )
695 | self.expandItem( legendLayer )
696 | self.setCurrentItem( legendLayer )
697 | self.updateLayerSet()
698 |
699 | def updateLayerStatus( self, item ):
700 | """ Update the layer status """
701 | if ( item ):
702 | if self.isLegendLayer( item ): # Is the item a layer item?
703 | for i in self.layers:
704 | if i.layer().id() == item.layerId:
705 | if item.checkState( 0 ) == Qt.Unchecked:
706 | i.setVisible( False )
707 | else:
708 | i.setVisible( True )
709 | self.canvas.setLayerSet( self.layers )
710 | return
711 |
712 | def currentItemChanged( self, newItem, oldItem ):
713 | """ Slot. Capture a new currentItem and emit a SIGNAL to inform the new type
714 | It could be used to activate/deactivate GUI buttons according the layer type
715 | """
716 | layerType = None
717 |
718 | if self.currentItem():
719 | if self.isLegendLayer( newItem ):
720 | layerType = newItem.canvasLayer.layer().type()
721 | self.canvas.setCurrentLayer( newItem.canvasLayer.layer() )
722 | else:
723 | layerType = newItem.parent().canvasLayer.layer().type()
724 | self.canvas.setCurrentLayer( newItem.parent().canvasLayer.layer() )
725 |
726 | self.emit( SIGNAL( "activeLayerChanged" ), layerType )
727 |
728 | def zoomToLayer( self ):
729 | """ Slot. Manage the zoomToLayer action in the context Menu """
730 | self.zoomToLegendLayer( self.currentItem() )
731 |
732 | def removeCurrentLayer( self ):
733 | """ Slot. Manage the removeCurrentLayer action in the context Menu """
734 | QgsMapLayerRegistry.instance().removeMapLayer( self.currentItem().canvasLayer.layer().id() )
735 | self.removeLegendLayer( self.currentItem() )
736 | self.updateLayerSet()
737 |
738 | def layerSymbology( self ):
739 | """ Change the features color of a vector layer """
740 | legendLayer = self.currentItem()
741 |
742 | if legendLayer.isVect == True:
743 | geom = legendLayer.canvasLayer.layer().geometryType() # QGis Geometry
744 | for i in self.layers:
745 | if i.layer().id() == legendLayer.layerId:
746 | color = QColorDialog.getColor( i.layer().rendererV2().symbols()[ 0 ].color(), self.pyQGisApp )
747 | break
748 | if color.isValid():
749 | pm = QPixmap()
750 | iconChild = QIcon()
751 | legendLayer.canvasLayer.layer().rendererV2().symbols()[ 0 ].setColor( color )
752 | self.canvas.refresh()
753 |
754 | def zoomToLegendLayer( self, legendLayer ):
755 | """ Zoom the map to a layer extent """
756 | for i in self.layers:
757 | if i.layer().id() == legendLayer.layerId:
758 | extent = i.layer().extent()
759 | extent.scale( 1.05 )
760 | self.canvas.setExtent( extent )
761 | self.canvas.refresh()
762 | break
763 |
764 | def removeLegendLayer( self, legendLayer ):
765 | """ Remove a layer item in the legend """
766 | if self.topLevelItemCount() == 1:
767 | self.clear()
768 | else: # Manage the currentLayer before the remove
769 | indice = self.indexOfTopLevelItem( legendLayer )
770 | if indice == 0:
771 | newCurrentItem = self.topLevelItem( indice + 1 )
772 | else:
773 | newCurrentItem = self.topLevelItem( indice - 1 )
774 |
775 | self.setCurrentItem( newCurrentItem )
776 | self.takeTopLevelItem( self.indexOfTopLevelItem( legendLayer ) )
777 |
778 | def removeAll( self ):
779 | """ Remove all legend items """
780 | self.clear()
781 | self.updateLayerSet()
782 |
783 | def updateLayerSet( self ):
784 | """ Update the LayerSet and set it to canvas """
785 | self.layers = self.getLayerSet()
786 | self.canvas.setLayerSet( self.layers )
787 |
788 | def getLayerSet( self ):
789 | """ Get the LayerSet by reading the layer items in the legend """
790 | layers = []
791 | for i in range( self.topLevelItemCount() ):
792 | layers.append( self.topLevelItem( i ).canvasLayer )
793 | return layers
794 |
795 | def activeLayer( self ):
796 | """ Return the selected layer """
797 | if self.currentItem():
798 | if self.isLegendLayer( self.currentItem() ):
799 | return self.currentItem().canvasLayer
800 | else:
801 | return self.currentItem().parent().canvasLayer
802 | else:
803 | return None
804 |
805 | def collapseAll( self ):
806 | """ Collapse all layer items in the legend """
807 | for i in range( self.topLevelItemCount() ):
808 | item = self.topLevelItem( i )
809 | self.collapseItem( item )
810 |
811 | def expandAll( self ):
812 | """ Expand all layer items in the legend """
813 | for i in range( self.topLevelItemCount() ):
814 | item = self.topLevelItem( i )
815 | self.expandItem( item )
816 |
817 | def isLegendLayer( self, item ):
818 | """ Check if a given item is a layer item """
819 | return not item.parent()
820 |
821 | def storeInitialPosition( self ):
822 | """ Store the layers order """
823 | self.__beforeDragStateLayers = self.getLayerIDs()
824 |
825 | def getLayerIDs( self ):
826 | """ Return a list with the layers ids """
827 | layers = []
828 | for i in range( self.topLevelItemCount() ):
829 | item = self.topLevelItem( i )
830 | layers.append( item.layerId )
831 | return layers
832 |
833 | def nextSibling( self, item ):
834 | """ Return the next layer item based on a given item """
835 | for i in range( self.topLevelItemCount() ):
836 | if item.layerId == self.topLevelItem( i ).layerId:
837 | break
838 | if i < self.topLevelItemCount():
839 | return self.topLevelItem( i + 1 )
840 | else:
841 | return None
842 |
843 | def moveItem( self, itemToMove, afterItem ):
844 | """ Move the itemToMove after the afterItem in the legend """
845 | itemToMove.storeAppearanceSettings() # Store settings in the moved item
846 | self.takeTopLevelItem( self.indexOfTopLevelItem( itemToMove ) )
847 | self.insertTopLevelItem( self.indexOfTopLevelItem( afterItem ) + 1, itemToMove )
848 | itemToMove.restoreAppearanceSettings() # Apply the settings again
849 | self.updatePropertiesWidget() # Regenerate all the QLabel widgets for displaying purposes
850 |
851 | def updatePropertiesWidget(self):
852 | """ Weird function to create QLabel widgets for refreshing the properties
853 | It is required to avoid a disgusting overlap in QLabel widgets
854 | """
855 | for i in range( self.topLevelItemCount() ):
856 | item = self.topLevelItem( i )
857 | item.displayLayerProperties()
858 |
859 | def checkLayerOrderUpdate( self ):
860 | """
861 | Check if the initial layers order is equal to the final one.
862 | This is used to refresh the legend in the release event.
863 | """
864 | self.__afterDragStateLayers = self.getLayerIDs()
865 | if self.__afterDragStateLayers != self.__beforeDragStateLayers:
866 | self.updateLayerSet()
867 |
868 | def yCoordAboveCenter( self, legendItem, ycoord ):
869 | """
870 | Return a bool to know if the ycoord is in the above center of the legendItem
871 |
872 | legendItem: The base item to get the above center and the below center
873 | ycoord: The coordinate of the comparison
874 | """
875 | rect = self.visualItemRect( legendItem )
876 | height = rect.height()
877 | top = rect.top()
878 | mid = top + ( height / 2 )
879 | if ( ycoord > mid ): # Bottom, remember the y-coordinate increases downwards
880 | return False
881 | else: # Top
882 | return True
883 |
884 | def normalizeLayerName( self, name ):
885 | """ Create an alias to put in the legend and avoid to repeat names """
886 | # Remove the extension
887 | if len( name ) > 4:
888 | if name[ -4 ] == '.':
889 | name = name[ :-4 ]
890 | return self.createUniqueName( name )
891 |
892 | def createUniqueName( self, name ):
893 | """ Avoid to repeat layers names """
894 | import re
895 | name_validation = re.compile( "\s\(\d+\)$", re.UNICODE ) # Strings like " (1)"
896 |
897 | bRepetida = True
898 | i = 1
899 | while bRepetida:
900 | bRepetida = False
901 |
902 | # If necessary add a sufix like " (1)" to avoid to repeat names in the legend
903 | for j in range( self.topLevelItemCount() ):
904 | item = self.topLevelItem( j )
905 | if item.text( 0 ) == name:
906 | bRepetida = True
907 | if name_validation.search( name ): # The name already has numeration
908 | name = name[ :-4 ] + ' (' + str( i ) + ')'
909 | else: # Add numeration because the name doesn't have it
910 | name = name + ' (' + str( i ) + ')'
911 | i += 1
912 | return name
913 |
914 |
915 | # Some helpful functions
916 | def formatNumber( number, precision=0, group_sep='.', decimal_sep=',' ):
917 | """
918 | number: Number to be formatted
919 | precision: Number of decimals
920 | group_sep: Miles separator
921 | decimal_sep: Decimal separator
922 | """
923 | number = ( '%.*f' % ( max( 0, precision ), number ) ).split( '.' )
924 | integer_part = number[ 0 ]
925 |
926 | if integer_part[ 0 ] == '-':
927 | sign = integer_part[ 0 ]
928 | integer_part = integer_part[ 1: ]
929 | else:
930 | sign = ''
931 |
932 | if len( number ) == 2:
933 | decimal_part = decimal_sep + number[ 1 ]
934 | else:
935 | decimal_part = ''
936 |
937 | integer_part = list( integer_part )
938 | c = len( integer_part )
939 |
940 | while c > 3:
941 | c -= 3
942 | integer_part.insert( c, group_sep )
943 |
944 | return sign + ''.join( integer_part ) + decimal_part
945 |
946 | def formatToDegrees( number ):
947 | """ Returns the degrees-minutes-seconds form of number """
948 | sign = ''
949 | if number < 0:
950 | number = math.fabs( number )
951 | sign = '-'
952 |
953 | deg = math.floor( number )
954 | minu = math.floor( ( number - deg ) * 60 )
955 | sec = ( ( ( number - deg ) * 60 ) - minu ) * 60
956 |
957 | return sign + "%.0f"%deg + u'° ' + "%.0f"%minu + "' " \
958 | + "%.2f"%sec + "\""
959 |
960 | def show_error(title, text):
961 | QMessageBox.critical(None, title, text,
962 | QMessageBox.Ok | QMessageBox.Default,
963 | QMessageBox.NoButton)
964 | print >> sys.stderr, 'E: Error. Exiting ...'
965 | print __doc__
966 | sys.exit(1)
967 |
968 |
969 | def main( argv ):
970 | print 'I: Starting viewer ...'
971 | app = SingletonApp( argv )
972 |
973 | dictOpts = { '-h':'', '-p':'5432', '-U':'', '-W':'', '-d':'', '-s':'public',
974 | '-t':'', '-g':'', 'type':'unknown', 'srid':'', 'col':'' }
975 |
976 | opts, args = getopt.getopt( sys.argv[1:], 'h:p:U:W:d:s:t:g:', [] )
977 | dictOpts.update( opts )
978 |
979 | if dictOpts['-t'] == '':
980 | print >> sys.stderr, 'E: Table name is required'
981 | print __doc__
982 | sys.exit( 1 )
983 |
984 | d = QSqlDatabase.addDatabase( "QPSQL", "PgSQLDb" )
985 | d.setHostName( dictOpts['-h'] )
986 | d.setPort( int( dictOpts['-p'] ) )
987 | d.setDatabaseName( dictOpts['-d'] )
988 | d.setUserName( dictOpts['-U'] )
989 | d.setPassword( dictOpts['-W'] )
990 |
991 | if d.open():
992 | print 'I: Database connection was succesfull'
993 |
994 | query = QSqlQuery( d )
995 | query.exec_( "SELECT Count(srid) FROM raster_columns WHERE r_table_schema = '%s' AND r_table_name = '%s'" % ( dictOpts['-s'], dictOpts['-t'] ) )
996 |
997 | if query.next() and query.value( 0 ).toBool(): # Raster layer (WKTRaster)!
998 | query.exec_( "SELECT srid, r_raster_column FROM raster_columns \
999 | WHERE r_table_schema = '%s' AND \
1000 | r_table_name = '%s' " % ( dictOpts['-s'], dictOpts['-t'] ) )
1001 | if query.next():
1002 | dictOpts[ 'srid' ] = str( query.value( 0 ).toString() )
1003 | dictOpts[ 'col' ] = str( query.value( 1 ).toString() )
1004 |
1005 | dictOpts['type'] = 'raster'
1006 | print 'I: Raster layer detected'
1007 |
1008 | else: # Vector layer?
1009 | query.exec_( "SELECT column_name FROM information_schema.columns \
1010 | WHERE table_schema = '%s' AND \
1011 | table_name = '%s' AND \
1012 | udt_name = 'geometry' LIMIT 1" % ( dictOpts['-s'], dictOpts['-t'] ) )
1013 |
1014 | if not query.next(): # Geography layer?
1015 | query.exec_( "SELECT column_name FROM information_schema.columns \
1016 | WHERE table_schema = '%s' AND \
1017 | table_name = '%s' AND \
1018 | udt_name = 'geography' LIMIT 1" % ( dictOpts['-s'], dictOpts['-t'] ) )
1019 |
1020 | if query.first(): # Vector layer!
1021 | dictOpts[ '-g' ] = str( query.value( 0 ).toString() )
1022 |
1023 | query.exec_( "SELECT srid FROM geometry_columns \
1024 | WHERE f_table_schema = '%s' AND \
1025 | f_table_name = '%s' " % ( dictOpts['-s'], dictOpts['-t'] ) )
1026 | if query.next():
1027 | dictOpts[ 'srid' ] = str( query.value( 0 ).toString() )
1028 |
1029 | dictOpts['type'] = 'vector'
1030 | print 'I: Vector layer detected'
1031 |
1032 | if not dictOpts[ 'type' ] == 'unknown': # The object is a layer
1033 | if app.is_running:
1034 | # Application already running, send message to load data
1035 | app.send_message( dictOpts )
1036 | else:
1037 | # Start the Viewer
1038 |
1039 | # QGIS libs init
1040 | QgsApplication.setPrefixPath(qgis_prefix, True)
1041 | QgsApplication.initQgis()
1042 |
1043 | # Open viewer
1044 | wnd = ViewerWnd( app, dictOpts )
1045 | wnd.move(100,100)
1046 | wnd.resize(400, 500)
1047 | wnd.show()
1048 |
1049 | retval = app.exec_()
1050 |
1051 | # Exit
1052 | QgsApplication.exitQgis()
1053 | print 'I: Exiting ...'
1054 | sys.exit(retval)
1055 | else:
1056 | show_error("Error when opening layer",
1057 | "Layer '%s.%s' doesn't exist. Be sure the selected object is either raster or vector layer." % (dictOpts['-s'], dictOpts['-t']))
1058 | else:
1059 | show_error("Connection error", "Error when connecting to database.")
1060 |
1061 | if __name__ == "__main__":
1062 | main( sys.argv )
1063 |
--------------------------------------------------------------------------------