├── 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 | --------------------------------------------------------------------------------