├── .gitignore
├── LICENSE
├── README.md
├── __init__.py
├── apiqtpl.py
├── catalogpl.py
├── catalogpl.svg
├── comandos_git.txt
├── create_zip_plugin.sh
├── legendlayer.py
├── legendlayerpl.py
├── managerloginkey.py
├── messagebarcancel.py
├── metadata.txt
├── pl_expressions.py
├── pl_scenes.qml
└── workertms.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore
2 | .directory
3 | *.pyc
4 | *.zip
5 | *.sh
6 | # Not Ignore
7 | !create_zip_plugin.sh
8 |
9 |
--------------------------------------------------------------------------------
/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 |
2 | [planetlab_logo]: https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Planet_logo_New.png/240px-Planet_logo_New.png
3 |
4 | ![][planetlab_logo]
5 | [PlanetLab](https://www.planet.com/explorers/)
6 |
7 | # Catalog Planet Lab Plugin QGIS
8 |
9 | This plugin lets you get images from the Planet Labs API(Version 1),
10 | by performing searches for images that intersect with the extent of the map window.
11 | It is a product from Planet Explorers program (https://www.planet.com/explorers/),
12 | it is not an official Planet Labs's plugin.
13 | You need a key from Planet Labs in order to use this plugin.
14 | This plugin will be create polygon layer (Catalog of images) from intersect with the extent of the map window.
15 | With this plugin, you can download full images(Analytic, UDM, ...), thumbnail images or
16 | add TMS images.
17 | See presentation: https://www.slideshare.net/LuizMotta3/catalog-planet-labs-plugin-for-qgis-v1
18 | Tested with QGIS 2.18.13
19 |
20 | ## Author
21 | Luiz Motta
22 |
23 | ## Changelog
24 | - 2017-12-20
25 | Fixed error ltgRoot in downloadImages(catalogpl.py).
26 | - 2017-11-12
27 | Change menus, catalog group, scene layer, count total dates. Fixed use in Windows
28 | - 2017-10-06
29 | Add groups of images with same date,
30 | add menu Open Form in images, clear cache of TMS, and
31 | add action 'Add selection' to pl_scenes
32 | - 2017-10-04
33 | Reverse images order in group catalog, add directories,
34 | tms, tif, thumbnail, inside download directory
35 | - 2017-10-03
36 | Fix cache_path when save TMS
37 | - 2017-10-02
38 | Change TMS by Server tiler XYZ to GDAL_WMS
39 | Fixed when the first use of plugin(not set the directory), not show the setting
40 | - 2017-10-01
41 | Changed checkbox to radiobutton for images, only one type of image for searching.
42 | - 2017-09-30
43 | Updated to API Planet V1
44 | - 2015-08-17
45 | Fixed message when error download and add message log
46 | - 2015-7-28
47 | Add context menu remove key
48 | - 2015-07-15
49 | Add context menu image full and TMS
50 | - 2015-07-12
51 | Update the checkLayerLegend(), remove clean register
52 | - 2015-07-09
53 | Add cancel for TMS for download
54 | - 2015-07-07
55 | Add TMS for download
56 | - 2015-06-12:
57 | Add feature for download images and thumbnails
58 | Add metadata in table and refactoring codes.
59 | - 2015-04-26:
60 | Create plugin.
61 |
--------------------------------------------------------------------------------
/__init__.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | /***************************************************************************
4 | Name : Catalog Planet Labs
5 | Description : Create catalog from Planet Labs
6 | Date : April, 2015
7 | copyright : (C) 2015 by Luiz Motta
8 | email : motta.luiz@gmail.com
9 |
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 |
22 | import os
23 |
24 | from PyQt4 import QtCore, QtGui
25 | from qgis import core as QgsCore, gui as QgsGui
26 |
27 | from catalogpl import CatalogPL
28 | from apiqtpl import API_PlanetLabs
29 |
30 | def classFactory(iface):
31 | return CatalogPLPlugin( iface )
32 |
33 | class CatalogPLPlugin:
34 |
35 | icon = QtGui.QIcon( os.path.join( os.path.dirname(__file__), 'catalogpl.svg' ) )
36 | pluginName = "Catalog Planet Labs"
37 |
38 | def __init__(self, iface):
39 |
40 | self.iface = iface
41 | self.name = u"&Catalog Planet Labs"
42 | self.msgBar = iface.messageBar()
43 | self.action = None
44 | self.ctl = CatalogPL( CatalogPLPlugin.icon )
45 |
46 | CatalogPL.copyExpression()
47 |
48 | def initGui(self):
49 | dataActions = [
50 | {
51 | 'isSepatator': False,
52 | 'name': 'Catalog Planet Labs',
53 | 'icon': QtGui.QIcon( CatalogPLPlugin.icon ),
54 | 'method': self.run
55 | },
56 | { 'isSepatator': True },
57 | {
58 | 'isSepatator': False,
59 | 'name': 'Setting...',
60 | 'icon': QgsCore.QgsApplication.getThemeIcon('/mActionOptions.svg'),
61 | 'method': self.config
62 | },
63 | { 'isSepatator': True },
64 | {
65 | 'isSepatator': False,
66 | 'name': 'Clear key',
67 | 'icon': QgsCore.QgsApplication.getThemeIcon('/mActionOptions.svg'),
68 | 'method': self.clearKey
69 | },
70 | {
71 | 'isSepatator': False,
72 | 'name': 'Copy key to Clipboard',
73 | 'icon': QgsCore.QgsApplication.getThemeIcon('/mActionOptions.svg'),
74 | 'method': self.clipboardKey
75 | }
76 | ]
77 |
78 | mw = self.iface.mainWindow()
79 | popupMenu = QtGui.QMenu( mw )
80 | for d in dataActions:
81 | if d['isSepatator']:
82 | a = QtGui.QAction( mw )
83 | a.setSeparator(True)
84 | else:
85 | a = QtGui.QAction( d['icon'], d['name'], mw )
86 | a.triggered.connect( d['method'] )
87 | self.iface.addPluginToRasterMenu( self.name, a )
88 | popupMenu.addAction( a )
89 | defaultAction = popupMenu.actions()[0]
90 | self.toolButton = QtGui.QToolButton()
91 | self.toolButton.setPopupMode( QtGui.QToolButton.MenuButtonPopup )
92 | self.toolButton.setMenu( popupMenu )
93 | self.toolButton.setDefaultAction( defaultAction )
94 |
95 | self.actionPopupMenu = self.iface.addToolBarWidget( self.toolButton )
96 | self.ctl.enableRun.connect( self.actionPopupMenu.setEnabled )
97 |
98 | def unload(self):
99 | self.iface.removePluginMenu( self.name, self.action )
100 | self.iface.removeToolBarIcon( self.action )
101 | del self.action
102 | del self.ctl
103 |
104 | @QtCore.pyqtSlot()
105 | def run(self):
106 | if self.iface.mapCanvas().layerCount() == 0:
107 | msg = "Need layer(s) in map"
108 | self.iface.messageBar().pushMessage( CatalogPLPlugin.pluginName, msg, QgsGui.QgsMessageBar.WARNING, 2 )
109 | return
110 |
111 | if not self.ctl.isHostLive:
112 | self.ctl.hostLive()
113 | if not self.ctl.isHostLive:
114 | return
115 |
116 | if not self.ctl.hasRegisterKey:
117 | self.ctl.registerKey()
118 | if not self.ctl.hasRegisterKey:
119 | return
120 |
121 | self.ctl.createLayerScenes()
122 |
123 | @QtCore.pyqtSlot()
124 | def config(self):
125 | self.ctl.settingImages()
126 |
127 | @QtCore.pyqtSlot()
128 | def clearKey(self):
129 | self.ctl.clearKey()
130 |
131 | @QtCore.pyqtSlot()
132 | def clipboardKey(self):
133 | self.ctl.clipboardKey()
134 |
--------------------------------------------------------------------------------
/apiqtpl.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | """
3 | /***************************************************************************
4 | Name : QtCore.Qt API for Catalog Planet Labs
5 | Description : API for Planet Labs
6 | Date : May, 2015
7 | copyright : (C) 2015 by Luiz Motta
8 | email : motta.luiz@gmail.com
9 |
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 |
22 | import json, datetime
23 |
24 | from PyQt4 import QtCore, QtGui, QtNetwork
25 |
26 | class AccessSite(QtCore.QObject):
27 |
28 | # Signals
29 | finished = QtCore.pyqtSignal( dict)
30 | send_data = QtCore.pyqtSignal(QtCore.QByteArray)
31 | status_download = QtCore.pyqtSignal(int, int)
32 | status_erros = QtCore.pyqtSignal(list)
33 |
34 | ErrorCodeAttribute = {
35 | 10: 'Canceled request',
36 | 400: 'Bad request syntax',
37 | 401: 'Unauthorized',
38 | 402: 'Payment required',
39 | 403: 'Forbidden',
40 | 404: 'Not found',
41 | 500: 'Internal error',
42 | 501: 'Not implemented',
43 | 502: 'Bad Gateway'
44 | }
45 |
46 | def __init__(self):
47 | super( AccessSite, self ).__init__()
48 | self.networkAccess = QtNetwork.QNetworkAccessManager(self)
49 | self.totalReady = self.reply = self.triedAuthentication = self.isKilled = None
50 | # Input by self.run
51 | self.credential = self.responseAllFinished = None
52 |
53 | def run(self, url, credential=None, responseAllFinished=True, json_request=None):
54 | if credential is None:
55 | credential = {'user': '', 'password': ''}
56 | ( self.credential, self.responseAllFinished ) = ( credential, responseAllFinished )
57 | self._connect()
58 | self.totalReady = 0
59 | self.isKilled = False
60 | request = QtNetwork.QNetworkRequest( url )
61 | if json_request is None:
62 | reply = self.networkAccess.get( request )
63 | else:
64 | request.setHeader( QtNetwork.QNetworkRequest.ContentTypeHeader, "application/json" )
65 | data = QtCore.QByteArray( json.dumps( json_request ) )
66 | reply = self.networkAccess.post( request, data )
67 | if reply is None:
68 | response = { 'isOk': False, 'message': "Network error", 'errorCode': -1 }
69 | self._connect( False )
70 | self.finished.emit( response )
71 | return
72 |
73 | self.triedAuthentication = False
74 | self.reply = reply
75 | self._connectReply()
76 |
77 | def kill(self):
78 | self.isKilled = True
79 |
80 | def isRunning(self):
81 | return ( not self.reply is None and self.reply.isRunning() )
82 |
83 | def _connect(self, isConnect=True):
84 | ss = [
85 | { 'signal': self.networkAccess.finished, 'slot': self.replyFinished },
86 | { 'signal': self.networkAccess.authenticationRequired, 'slot': self.authenticationRequired }
87 | ]
88 | if isConnect:
89 | for item in ss:
90 | item['signal'].connect( item['slot'] )
91 | else:
92 | for item in ss:
93 | item['signal'].disconnect( item['slot'] )
94 |
95 | def _connectReply(self, isConnect=True):
96 | ss = [
97 | { 'signal': self.reply.readyRead, 'slot': self.readyRead },
98 | { 'signal': self.reply.downloadProgress, 'slot': self.downloadProgress },
99 | { 'signal': self.reply.sslErrors, 'slot': self.sslErrors }
100 | ]
101 | if isConnect:
102 | for item in ss:
103 | item['signal'].connect( item['slot'] )
104 | else:
105 | for item in ss:
106 | item['signal'].disconnect( item['slot'] )
107 |
108 | def _clearConnect(self):
109 | self._connect( False ) # self.reply.close() -> emit signal self.networkAccess.finished
110 | self._connectReply( False )
111 | self.reply.close()
112 | self.reply.deleteLater();
113 | del self.reply
114 | self.reply = None
115 |
116 | def _redirectionReply(self, url):
117 | self._clearConnect()
118 | self._connect()
119 | if url.isRelative():
120 | url = url.resolved( url )
121 |
122 | request = QtNetwork.QNetworkRequest( url )
123 | reply = self.networkAccess.get( request )
124 | if reply is None:
125 | response = { 'isOk': False, 'message': "Netwok error", 'errorCode': -1 }
126 | self._connect( False )
127 | self.finished.emit( response )
128 | return
129 |
130 | self.reply = reply
131 | self._connectReply()
132 |
133 | def _errorCodeAttribute(self, code):
134 | msg = 'Error network' if not code in self.ErrorCodeAttribute.keys() else AccessSite.ErrorCodeAttribute[ code ]
135 | response = { 'isOk': False, 'message': msg, 'errorCode': code }
136 | self._clearConnect()
137 | self.finished.emit( response )
138 |
139 | @QtCore.pyqtSlot(QtNetwork.QNetworkReply)
140 | def replyFinished(self, reply) :
141 | if self.isKilled:
142 | self._errorCodeAttribute(10)
143 |
144 | if reply.error() != QtNetwork.QNetworkReply.NoError :
145 | response = { 'isOk': False, 'message': reply.errorString(), 'errorCode': reply.error() }
146 | self._clearConnect()
147 | self.finished.emit( response )
148 | return
149 |
150 | urlRedir = reply.attribute( QtNetwork.QNetworkRequest.RedirectionTargetAttribute )
151 | if not urlRedir is None and urlRedir != reply.url():
152 | self._redirectionReply( urlRedir )
153 | return
154 |
155 | codeAttribute = reply.attribute( QtNetwork.QNetworkRequest.HttpStatusCodeAttribute )
156 | if codeAttribute != 200:
157 | self._errorCodeAttribute( codeAttribute )
158 | return
159 |
160 | statusRequest = {
161 | 'contentTypeHeader': reply.header( QtNetwork.QNetworkRequest.ContentTypeHeader ),
162 | 'lastModifiedHeader': reply.header( QtNetwork.QNetworkRequest.LastModifiedHeader ),
163 | 'contentLengthHeader': reply.header( QtNetwork.QNetworkRequest.ContentLengthHeader ),
164 | 'statusCodeAttribute': reply.attribute( QtNetwork.QNetworkRequest.HttpStatusCodeAttribute ),
165 | 'reasonPhraseAttribute': reply.attribute( QtNetwork.QNetworkRequest.HttpReasonPhraseAttribute )
166 | }
167 | response = { 'isOk': True, 'statusRequest': statusRequest }
168 | if self.responseAllFinished:
169 | response[ 'data' ] = reply.readAll()
170 | else:
171 | response[ 'totalReady' ] = self.totalReady
172 |
173 | self._clearConnect()
174 | self.finished.emit( response )
175 |
176 | @QtCore.pyqtSlot(QtNetwork.QNetworkReply, QtNetwork.QAuthenticator)
177 | def authenticationRequired (self, reply, authenticator):
178 | if not self.triedAuthentication:
179 | authenticator.setUser( self.credential['user'] )
180 | authenticator.setPassword( self.credential['password'] )
181 | self.triedAuthentication = True
182 | else:
183 | self._errorCodeAttribute( 401 )
184 |
185 | @QtCore.pyqtSlot()
186 | def readyRead(self):
187 | if self.isKilled:
188 | self._errorCodeAttribute(10)
189 | return
190 |
191 | if self.responseAllFinished:
192 | return
193 |
194 | urlRedir = self.reply.attribute( QtNetwork.QNetworkRequest.RedirectionTargetAttribute )
195 | if not urlRedir is None and urlRedir != self.reply.url():
196 | self._redirectionReply( urlRedir )
197 | return
198 |
199 | codeAttribute = self.reply.attribute( QtNetwork.QNetworkRequest.HttpStatusCodeAttribute )
200 | if codeAttribute != 200:
201 | self._errorCodeAttribute( codeAttribute )
202 | return
203 |
204 | data = self.reply.readAll()
205 | if data is None:
206 | return
207 | self.totalReady += len ( data )
208 | self.send_data.emit( data )
209 |
210 | @QtCore.pyqtSlot(int, int)
211 | def downloadProgress(self, bytesReceived, bytesTotal):
212 | if self.isKilled:
213 | self._errorCodeAttribute(10)
214 | else:
215 | self.status_download.emit( bytesReceived, bytesTotal )
216 |
217 | @QtCore.pyqtSlot( list )
218 | def sslErrors(self, errors):
219 | lstErros = map( lambda e: e.errorString(), errors )
220 | self.status_erros.emit( lstErros )
221 | self.reply.ignoreSslErrors()
222 |
223 |
224 | class API_PlanetLabs(QtCore.QObject):
225 |
226 | errorCodeLimitOK = (201, 207) # https://en.wikipedia.org/wiki/List_of_HTTP_status_codes (2107-09-30)
227 | errorCodeDownloads = { # Planet DOC (2107-09-30)
228 | 299: 'Download quota has been exceeded',
229 | 429: 'Request has been denied due to exceeding rate limits.'
230 | }
231 | validKey = None
232 | urlRoot = "https://api.planet.com"
233 | urlQuickSearch = "https://api.planet.com/data/v1/quick-search"
234 | urlThumbnail = "https://api.planet.com/data/v1/item-types/{item_type}/items/{item_id}/thumb"
235 | urlTMS = "https://tiles.planet.com/data/v1/{item_type}/{item_id}/{{z}}/{{x}}/{{y}}.png"
236 | urlAssets = "https://api.planet.com/data/v1/item-types/{item_type}/items/{item_id}/assets"
237 |
238 | def __init__(self):
239 | super( API_PlanetLabs, self ).__init__()
240 | self.access = AccessSite()
241 | self.currentUrl = None
242 |
243 | def _clearResponse(self, response):
244 | if response.has_key('data'):
245 | response['data'].clear()
246 | del response[ 'data' ]
247 | del response[ 'statusRequest' ]
248 |
249 | def kill(self):
250 | self.access.kill()
251 |
252 | def isRunning(self):
253 | return self.access.isRunning()
254 |
255 | def isHostLive(self, setFinished):
256 | @QtCore.pyqtSlot(dict)
257 | def finished( response):
258 | self.access.finished.disconnect( finished )
259 | if response['isOk']:
260 | response[ 'isHostLive' ] = True
261 | self._clearResponse( response )
262 | else:
263 | if response['errorCode'] == QtNetwork.QNetworkReply.HostNotFoundError:
264 | response[ 'isHostLive' ] = False
265 | response[ 'message' ] += "\nURL = %s" % API_PlanetLabs.urlRoot
266 | else:
267 | response[ 'isHostLive' ] = True
268 |
269 | setFinished( response )
270 |
271 | self.currentUrl = API_PlanetLabs.urlRoot
272 | url = QtCore.QUrl( self.currentUrl )
273 | self.access.finished.connect( finished )
274 | credential = { 'user': '', 'password': ''}
275 | self.access.run( url, credential )
276 |
277 | def setKey(self, key, setFinished):
278 | @QtCore.pyqtSlot(dict)
279 | def finished( response):
280 | self.access.finished.disconnect( finished )
281 | if response['isOk']:
282 | API_PlanetLabs.validKey = key
283 | self._clearResponse( response )
284 |
285 | setFinished( response )
286 |
287 | self.currentUrl = API_PlanetLabs.urlRoot
288 | url = QtCore.QUrl( self.currentUrl )
289 | self.access.finished.connect( finished )
290 | credential = { 'user': key, 'password': ''}
291 | self.access.run( url, credential )
292 |
293 | def getUrlScenes(self, json_request, setFinished):
294 | @QtCore.pyqtSlot(dict)
295 | def finished( response):
296 | self.access.finished.disconnect( finished )
297 | if response[ 'isOk' ]:
298 | data = json.loads( str( response['data'] ) )
299 | response[ 'url_scenes' ] = data['_links']['_self']
300 | response['total'] = len( data['features'] )
301 |
302 | data.clear()
303 | self._clearResponse( response )
304 |
305 | setFinished( response )
306 |
307 | self.currentUrl = API_PlanetLabs.urlQuickSearch
308 | url = QtCore.QUrl( self.currentUrl )
309 | self.access.finished.connect( finished )
310 | credential = { 'user': API_PlanetLabs.validKey, 'password': ''}
311 | self.access.run( url, credential, json_request=json_request )
312 |
313 | def getScenes(self, url, setFinished):
314 | @QtCore.pyqtSlot(dict)
315 | def finished( response):
316 | self.access.finished.disconnect( finished )
317 | if response[ 'isOk' ]:
318 | data = json.loads( str( response[ 'data' ] ) )
319 | response[ 'url' ] = data[ '_links' ][ '_next' ]
320 | response[ 'scenes' ] = data[ 'features' ]
321 | self._clearResponse( response )
322 |
323 | setFinished( response )
324 |
325 | self.currentUrl = url
326 | url = QtCore.QUrl.fromEncoded( url )
327 | self.access.finished.connect( finished )
328 | credential = { 'user': API_PlanetLabs.validKey, 'password': '' }
329 | self.access.run( url, credential )
330 |
331 | def getAssetsStatus(self, item_type, item_id, setFinished):
332 | @QtCore.pyqtSlot(dict)
333 | def finished( response):
334 | def setStatus(asset):
335 | def getDateTimeFormat(d):
336 | dt = datetime.datetime.strptime( d, "%Y-%m-%dT%H:%M:%S.%f")
337 | return dt.strftime( formatDateTime )
338 |
339 | key = "a_{0}".format( asset )
340 | response['assets_status'][ key ] = {}
341 | r = response['assets_status'][ key ]
342 | if not data.has_key( asset ):
343 | r['status'] = "*None*"
344 | return
345 | if data[ asset ].has_key('status'):
346 | r['status'] = data[ asset ]['status']
347 | if data[ asset ].has_key('_permissions'):
348 | permissions = ",".join( data[ asset ]['_permissions'])
349 | r['permissions'] = permissions
350 | if data[ asset ].has_key('expires_at'):
351 | r['expires_at'] = getDateTimeFormat( data[ asset ]['expires_at'] )
352 | if data[ asset ].has_key('_links'):
353 | if data[ asset ]['_links'].has_key('activate'):
354 | r['activate'] = data[ asset ]['_links']['activate']
355 | if data[ asset ].has_key('location'):
356 | r['location'] = data[ asset ]['location']
357 |
358 | self.access.finished.disconnect( finished )
359 | if response[ 'isOk' ]:
360 | formatDateTime = '%Y-%m-%d %H:%M:%S'
361 | date_time = datetime.datetime.now().strftime( formatDateTime )
362 | response['assets_status'] = {
363 | 'date_calculate': date_time,
364 | 'url': self.currentUrl
365 | }
366 | data = json.loads( str( response[ 'data' ] ) )
367 | setStatus('analytic')
368 | setStatus('udm')
369 | self._clearResponse( response )
370 |
371 | setFinished( response )
372 |
373 | url = API_PlanetLabs.urlAssets.format(item_type=item_type, item_id=item_id)
374 | self.currentUrl = url
375 | url = QtCore.QUrl.fromEncoded( url )
376 |
377 | self.access.finished.connect( finished )
378 | credential = { 'user': API_PlanetLabs.validKey, 'password': ''}
379 | self.access.run( url, credential )
380 |
381 | def getThumbnail(self, item_id, item_type, setFinished):
382 | @QtCore.pyqtSlot(dict)
383 | def finished( response ):
384 | self.access.finished.disconnect( finished )
385 | if response['isOk']:
386 | pixmap = QtGui.QPixmap()
387 | pixmap.loadFromData( response[ 'data' ] )
388 | response[ 'pixmap' ] = pixmap
389 | self._clearResponse( response )
390 |
391 | setFinished( response )
392 |
393 | url = API_PlanetLabs.urlThumbnail.format( item_type=item_type, item_id=item_id )
394 | self.currentUrl = url
395 | url = QtCore.QUrl( url )
396 | self.access.finished.connect( finished )
397 | credential = { 'user': API_PlanetLabs.validKey, 'password': ''}
398 | self.access.run( url, credential )
399 |
400 | def activeAsset(self, url, setFinished):
401 | @QtCore.pyqtSlot(dict)
402 | def finished( response ):
403 | self.access.finished.disconnect( finished )
404 | if response['isOk']:
405 | self._clearResponse( response )
406 | setFinished( response ) # response[ 'totalReady' ]
407 |
408 | url = QtCore.QUrl.fromEncoded( url )
409 | url = QtCore.QUrl( url )
410 | self.access.finished.connect( finished )
411 | credential = { 'user': API_PlanetLabs.validKey, 'password': ''}
412 | self.access.run( url, credential )
413 |
414 | def saveImage(self, url, setFinished, setSave, setProgress):
415 | @QtCore.pyqtSlot(dict)
416 | def finished( response ):
417 | self.access.finished.disconnect( finished )
418 | if response['isOk']:
419 | self._clearResponse( response )
420 | setFinished( response ) # response[ 'totalReady' ]
421 |
422 | url = QtCore.QUrl.fromEncoded( url )
423 | self.access.finished.connect( finished )
424 | self.access.send_data.connect( setSave )
425 | self.access.status_download.connect( setProgress )
426 | credential = { 'user': API_PlanetLabs.validKey, 'password': ''}
427 | self.access.run( url, credential, False )
428 |
429 | @staticmethod
430 | def getUrlFilterScenesOrtho(filters):
431 | items = []
432 | for item in filters.iteritems():
433 | skey = str( item[0] )
434 | svalue = str( item[1] )
435 | items.append( ( skey, svalue ) )
436 |
437 | url = QtCore.QUrl( API_PlanetLabs.urlScenesOrtho) # urlScenesRapideye
438 | url.setQueryItems( items )
439 |
440 | return url.toEncoded()
441 |
442 | @staticmethod
443 | def getValue(jsonMetadataFeature, keys):
444 | dicMetadata = jsonMetadataFeature
445 | if not isinstance( jsonMetadataFeature, dict):
446 | dicMetadata = json.loads( jsonMetadataFeature )
447 | msgError = None
448 | e_keys = map( lambda item: "'%s'" % item, keys )
449 | try:
450 | value = reduce( lambda d, k: d[ k ], [ dicMetadata ] + keys )
451 | except KeyError as e:
452 | msgError = "Catalog Planet: Have invalid key: %s" % ' -> '.join( e_keys)
453 | except TypeError as e:
454 | msgError = "Catalog Planet: The last key is invalid: %s" % ' -> '.join( e_keys)
455 |
456 | if msgError is None and isinstance( value, dict):
457 | msgError = "Catalog Planet: Missing key: %s" % ' -> '.join( e_keys)
458 |
459 | return ( True, value ) if msgError is None else ( False, msgError )
460 |
461 | @staticmethod
462 | def getTextTreeMetadata( jsonMetadataFeature ):
463 | def fill_item(strLevel, value):
464 | if not isinstance( value, ( dict, list ) ):
465 | items[-1] += ": %s" % value
466 | return
467 |
468 | if isinstance( value, dict ):
469 | for key, val in sorted( value.iteritems() ):
470 | items.append( "%s%s" % ( strLevel, key ) )
471 | strLevel += signalLevel
472 | fill_item( strLevel, val )
473 | strLevel = strLevel[ : -1 * len( signalLevel ) ]
474 | return
475 |
476 | if isinstance( value, list ):
477 | for val in value:
478 | if not isinstance( value, ( dict, list ) ):
479 | items[-1] += ": %s" % value
480 | else:
481 | text = '[dict]' if isinstance( value, dict ) else '[list]'
482 | items.append( "%s%s" % ( strLevel, text ) )
483 | strLevel += signalLevel
484 | fill_item( strLevel, val )
485 | strLevel = strLevel[ : -1 * len( signalLevel ) ]
486 |
487 | signalLevel = "- "
488 | items = []
489 | fill_item( '', json.loads( jsonMetadataFeature ) )
490 |
491 | return '\n'.join( items )
492 |
493 | @staticmethod
494 | def getHtmlTreeMetadata(value, html):
495 | if isinstance( value, dict ):
496 | html += "
"
497 | for key, val in sorted( value.iteritems() ):
498 | if not isinstance( val, dict ):
499 | html += "
%s: %s
" % ( key, val )
500 | else:
501 | html += "
%s
" % key
502 | html = API_PlanetLabs.getHtmlTreeMetadata( val, html )
503 | html += "