diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index c276715..2de32b2 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,9 +1,9 @@
---
default_language_version:
python: "python3"
-fail_fast: true
+fail_fast: false
ci:
- skip: ["poetry-lock", "prospector", "mypy"]
+ skip: ["poetry-lock", "prospector", "mypy", "docs"]
repos:
- repo: "https://github.com/pre-commit/pre-commit-hooks"
@@ -60,6 +60,11 @@ repos:
language: "system"
types: ["python"]
+ - id: "docs"
+ name: "docs"
+ entry: "make docs"
+ language: "system"
+
- repo: "https://github.com/pmav99/poetry11"
rev: "de1e372981c75860c4d8b86c6368e9a9d8a88450"
hooks:
diff --git a/.prospector.yml b/.prospector.yml
index 00bf42f..eb4c57f 100644
--- a/.prospector.yml
+++ b/.prospector.yml
@@ -17,6 +17,7 @@ ignore-paths:
# - "test"
pylint:
+ run: false
disable:
- "bad-continuation" # there is a conflict with black in function arguments
- "empty-docstring"
diff --git a/.readthedocs.yml b/.readthedocs.yml
new file mode 100644
index 0000000..d13bdde
--- /dev/null
+++ b/.readthedocs.yml
@@ -0,0 +1,20 @@
+# .readthedocs.yaml
+# Read the Docs configuration file
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+---
+
+version: 2
+
+build:
+ os: ubuntu-20.04
+ tools:
+ python: '3.10'
+
+sphinx:
+ configuration: docs/source/conf.py
+
+python:
+ install:
+ - method: pip
+ path: .
+ - requirements: requirements/requirements-dev.txt
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..9857d08
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc.
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+
+ Copyright (C)
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ ADCIRCPy Copyright (C) 2021 Jaime R Calzada
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+.
diff --git a/Makefile b/Makefile
index 33bd0ef..f53f773 100644
--- a/Makefile
+++ b/Makefile
@@ -1,9 +1,13 @@
-.PHONY: list
+.PHONY: list docs
+
list:
@LC_ALL=C $(MAKE) -pRrq -f $(lastword $(MAKEFILE_LIST)) : 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | sort | egrep -v -e '^[^[:alnum:]]' -e '^$@$$'
lint:
- prospector --absolute-paths --no-external-config --profile-path .prospector.yaml -w profile-validator -W pydocstyle
+ prospector --absolute-paths --no-external-config --profile-path .prospector.yaml -w profile-validator searvey
mypy:
dmypy run searvey
+
+docs:
+ make -C docs html
diff --git a/README.md b/README.md
index 7ae67d7..cf1439c 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,8 @@
# searvey
-[![pre-commit.ci status](https://results.pre-commit.ci/badge/github/oceanmodeling/searvey/master.svg)](https://results.pre-commit.ci/latest/github/oceanmodeling/searvey/master)
+[![pre-commit.ci](https://results.pre-commit.ci/badge/github/oceanmodeling/searvey/master.svg)](https://results.pre-commit.ci/latest/github/oceanmodeling/searvey/master)
[![tests](https://github.com/oceanmodeling/searvey/actions/workflows/run_tests.yml/badge.svg)](https://github.com/oceanmodeling/searvey/actions/workflows/run_tests.yml)
+[![readthedocs](https://readthedocs.org/projects/pip/badge/)](https://readthedocs.org/projects/searvey)
Searvey aims to provide the following functionality:
@@ -10,161 +11,10 @@ Searvey aims to provide the following functionality:
- Real time data analysis/clean up to facilitate comparison with numerical
models.
-- On demand data retrieval from multiple sources.
+- On demand data retrieval from multiple sources including:
-## Usage
-
-#### retrieve data products from the U.S. Center for Operational Oceanographic Products and Services (CO-OPS)
-
-The [Center for Operational Oceanographic Products and Services (CO-OPS)](https://tidesandcurrents.noaa.gov)
-maintains and operates a large array of tidal buoys and oceanic weather stations
-that measure water and atmospheric variables
-across the coastal United States. CO-OPS provides
-several [data products](https://tidesandcurrents.noaa.gov/products.html)
-including hourly water levels, tidal datums and predictions, and trends in sea
-level over time.
-
-A list of CO-OPS stations can be retrieved with the `coops_stations()` function.
-
-```python
-from searvey.coops import coops_stations
-
-coops_stations()
-```
-
-```
- nws_id name state status removed geometry
-nos_id
-1600012 46125 QREB buoy active POINT (122.62500 37.75000)
-1619910 SNDP5 Sand Island, Midway Islands active POINT (-177.37500 28.21875)
-1630000 APRP7 Apra Harbor, Guam active POINT (144.62500 13.44531)
-1631428 PGBP7 Pago Bay, Guam active POINT (144.75000 13.42969)
-1770000 NSTP6 Pago Pago, American Samoa active POINT (-170.75000 -14.27344)
-... ... ... ... ... ... ...
-8423898 FTPN3 Fort Point NH discontinued 2020-04-13 00:00:00,2014-08-05 00:00:00,2012-0... POINT (-70.68750 43.06250)
-8726667 MCYF1 Mckay Bay Entrance FL discontinued 2020-05-20 00:00:00,2019-03-08 00:00:00,2017-0... POINT (-82.43750 27.90625)
-8772447 FCGT2 Freeport TX discontinued 2020-05-24 18:45:00,2018-10-10 21:50:00,2018-1... POINT (-95.31250 28.93750)
-9087079 GBWW3 Green Bay WI discontinued 2020-10-28 13:00:00,2007-08-06 23:59:00,2007-0... POINT (-88.00000 44.53125)
-8770570 SBPT2 Sabine Pass North TX discontinued 2021-01-18 00:00:00,2020-09-30 15:45:00,2020-0... POINT (-93.87500 29.73438)
-
-[435 rows x 6 columns]
-```
-
-Additionally, you can use a Shapely `Polygon` or `MultiPolygon` to constrain the
-stations query to a specific region:
-
-```python
-from shapely.geometry import Polygon
-from searvey.coops import coops_stations_within_region
-
-region = Polygon(...)
-
-coops_stations_within_region(region=region)
-```
-
-```
- nws_id name state removed geometry
-nos_id
-8651370 DUKN7 Duck NC NaT POINT (-75.75000 36.18750)
-8652587 ORIN7 Oregon Inlet Marina NC NaT POINT (-75.56250 35.78125)
-8654467 HCGN7 USCG Station Hatteras NC NaT POINT (-75.68750 35.21875)
-8656483 BFTN7 Beaufort, Duke Marine Lab NC NaT POINT (-76.68750 34.71875)
-8658120 WLON7 Wilmington NC NaT POINT (-77.93750 34.21875)
-8658163 JMPN7 Wrightsville Beach NC NaT POINT (-77.81250 34.21875)
-8661070 MROS1 Springmaid Pier SC NaT POINT (-78.93750 33.65625)
-8662245 NITS1 Oyster Landing (N Inlet Estuary) SC NaT POINT (-79.18750 33.34375)
-8665530 CHTS1 Charleston, Cooper River Entrance SC NaT POINT (-79.93750 32.78125)
-8670870 FPKG1 Fort Pulaski GA NaT POINT (-80.87500 32.03125)
-```
-
-##### retrieve CO-OPS data product for a specific station
-
-```python
-from datetime import datetime
-from searvey.coops import COOPS_Station
-
-station = COOPS_Station(8632200)
-station.product('water_level', start_date=datetime(2018, 9, 13),
- end_date=datetime(2018, 9, 16, 12))
-```
-
-```
-
-Dimensions: (nos_id: 1, t: 841)
-Coordinates:
- * nos_id (nos_id) int64 8632200
- * t (t) datetime64[ns] 2018-09-13 ... 2018-09-16T12:00:00
- nws_id (nos_id)
-Dimensions: (nos_id: 10, t: 11)
-Coordinates:
- * nos_id (nos_id) int64 8651370 8652587 8654467 ... 8662245 8665530 8670870
- * t (t) datetime64[ns] 2022-03-08T14:48:00 ... 2022-03-08T15:48:00
- nws_id (nos_id) Path:
)
# -- Project information -----------------------------------------------------
-metadata = config.read_configuration("../../setup.cfg")["metadata"]
+metadata = toml.load("../../pyproject.toml")["tool"]["poetry"]
project = metadata["name"]
-author = metadata["author"]
-copyright = f"2021, {author}"
+copyright = f"{datetime.date.today().year}, https://github.com/oceanmodeling"
# The full version, including alpha/beta/rc tags
try:
release = Version.from_any_vcs().serialize()
except RuntimeError:
- release = os.environ.get("VERSION")
+ release = os.environ.get("VERSION", "0.0.0")
# -- General configuration ---------------------------------------------------
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 09a1e58..a2f0dd0 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -5,3 +5,4 @@
installation
coops
+ ioc
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index 2f6f464..49d54f7 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -1,7 +1,7 @@
Installation
============
-To install ``searvey``, you need `Python greater than or equal to version 3.6 `_.
+To install ``searvey``, you need `Python greater than or equal to version 3.9 `_.
Once you have installed Python, and presumably have ``pip`` that comes with the default installation,
you can run the following command to install ``searvey``:
diff --git a/docs/source/ioc.rst b/docs/source/ioc.rst
new file mode 100644
index 0000000..85f5a29
--- /dev/null
+++ b/docs/source/ioc.rst
@@ -0,0 +1,8 @@
+IOC
+===
+
+API
+---
+
+.. autofunction:: searvey.ioc.get_ioc_stations
+.. autofunction:: searvey.ioc.get_ioc_data
diff --git a/examples/IOC_data.ipynb b/examples/IOC_data.ipynb
new file mode 100644
index 0000000..994b655
--- /dev/null
+++ b/examples/IOC_data.ipynb
@@ -0,0 +1,233 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "65e90d8f-6d7d-4fd2-b700-2ab7854ceb55",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "import logging\n",
+ "\n",
+ "import shapely\n",
+ "import geopandas as gpd\n",
+ "import matplotlib.pyplot as plt\n",
+ "import pandas as pd\n",
+ "import xarray as xr\n",
+ "\n",
+ "from searvey import ioc\n",
+ "\n",
+ "logging.basicConfig(\n",
+ " level=20,\n",
+ " style=\"{\",\n",
+ " format=\"{asctime:s}; {levelname:8s}; {threadName:23s}; {name:<25s} {lineno:5d}; {message:s}\",\n",
+ ")\n",
+ "\n",
+ "logging.getLogger(\"urllib3\").setLevel(30)\n",
+ "logging.getLogger(\"parso\").setLevel(30)\n",
+ "\n",
+ "logger = logging.getLogger(__name__)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3d77b75b-17db-45e7-933d-ea7f79351f28",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2022-06-08T12:43:45.973799Z",
+ "iopub.status.busy": "2022-06-08T12:43:45.973432Z",
+ "iopub.status.idle": "2022-06-08T12:43:51.596147Z",
+ "shell.execute_reply": "2022-06-08T12:43:51.595523Z",
+ "shell.execute_reply.started": "2022-06-08T12:43:45.973779Z"
+ },
+ "tags": []
+ },
+ "source": [
+ "## Retrieve Station Metadata"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f0cb443c-b8a9-4abc-9bb1-e0c6e262e48b",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "ioc_stations = ioc.get_ioc_stations()\n",
+ "ioc_stations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "1fabbee2-b165-46a6-b69f-c3bb93972e80",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "figure, axis = plt.subplots(1, 1)\n",
+ "figure.set_size_inches(12, 12 / 1.61803398875)\n",
+ "\n",
+ "countries = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))\n",
+ "_ = countries.plot(color='lightgrey', ax=axis, zorder=-1)\n",
+ "_ = ioc_stations.plot(ax=axis)\n",
+ "_ = axis.set_title(f'all IOC stations')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "10cf3a7b-8b1b-4b4d-a77e-c3d4f21e743e",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "ioc_stations.columns"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d34d10c6-8354-451d-8580-169e2cc001cf",
+ "metadata": {
+ "execution": {
+ "iopub.execute_input": "2022-06-08T12:50:03.778509Z",
+ "iopub.status.busy": "2022-06-08T12:50:03.778244Z",
+ "iopub.status.idle": "2022-06-08T12:50:03.811163Z",
+ "shell.execute_reply": "2022-06-08T12:50:03.810727Z",
+ "shell.execute_reply.started": "2022-06-08T12:50:03.778483Z"
+ },
+ "tags": []
+ },
+ "source": [
+ "## Retrieve station metadata from arbitrary polygon"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "85bb38cc-652b-4d7e-9d1a-8fb8edaecc9c",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "east_coast = shapely.geometry.box(-85, 25, -65, 45)\n",
+ "east_coast\n",
+ "\n",
+ "east_stations = ioc.get_ioc_stations(region=east_coast)\n",
+ "east_stations"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3e19b4f0-665a-40d7-b070-91be9b359229",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "east_stations[~east_stations.contacts.str.contains(\"NOAA\")]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "28065b99-94cd-4182-ba27-95d6cd61b4a3",
+ "metadata": {},
+ "source": [
+ "## Retrieve IOC station data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f2499f27-a993-4906-83d5-43374a92bb58",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "east_data = ioc.get_ioc_data(\n",
+ " ioc_metadata=east_stations,\n",
+ " endtime=\"2020-05-30\",\n",
+ " period=3,\n",
+ ")\n",
+ "east_data"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "5cf3a9bb-af44-4935-9c0d-0abc51dffed3",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "def drop_all_nan_vars(ds: xr.Dataset) -> xr.Dataset:\n",
+ " for var in ds.data_vars:\n",
+ " if ds[var].notnull().sum() == 0:\n",
+ " ds = ds.drop_vars(var)\n",
+ " return ds\n",
+ "\n",
+ "ds = drop_all_nan_vars(east_data.sel(ioc_code=\"setp1\"))\n",
+ "ds"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "bcf72745-a293-4850-9a79-0191569bd556",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "fix, axes = plt.subplots(1, 1)\n",
+ "\n",
+ "_ = ds.prs.plot(ax=axes)\n",
+ "_ = ds.rad.plot(ax=axes)\n",
+ "_ = ds.ra2.plot(ax=axes)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "acaf9a46-e5bf-4b52-a73f-05566219bed1",
+ "metadata": {
+ "tags": []
+ },
+ "outputs": [],
+ "source": [
+ "ds.where(ds.country == \"Bahamas\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "searvey",
+ "language": "python",
+ "name": "searvey"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.9.12"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/poetry.lock b/poetry.lock
index 5db4b58..07d4ce4 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -212,6 +212,20 @@ category = "dev"
optional = false
python-versions = ">=3.5"
+[[package]]
+name = "deprecated"
+version = "1.2.13"
+description = "Python @deprecated decorator to deprecate old python classes, functions or methods."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
+[package.dependencies]
+wrapt = ">=1.10,<2"
+
+[package.extras]
+dev = ["tox", "bump2version (<1)", "sphinx (<2)", "importlib-metadata (<3)", "importlib-resources (<4)", "configparser (<5)", "sphinxcontrib-websupport (<2)", "zipp (<2)", "PyTest (<5)", "PyTest-Cov (<2.6)", "pytest", "pytest-cov"]
+
[[package]]
name = "dill"
version = "0.3.5.1"
@@ -247,6 +261,17 @@ category = "dev"
optional = false
python-versions = "*"
+[[package]]
+name = "dunamai"
+version = "1.12.0"
+description = "Dynamic version generation"
+category = "dev"
+optional = false
+python-versions = ">=3.5,<4.0"
+
+[package.dependencies]
+packaging = ">=20.9"
+
[[package]]
name = "entrypoints"
version = "0.4"
@@ -359,6 +384,24 @@ pandas = ">=0.25.0"
pyproj = ">=2.2.0"
shapely = ">=1.6"
+[[package]]
+name = "html5lib"
+version = "1.1"
+description = "HTML parser based on the WHATWG HTML specification"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+six = ">=1.9"
+webencodings = "*"
+
+[package.extras]
+all = ["genshi", "chardet (>=2.2)", "lxml"]
+chardet = ["chardet (>=2.2)"]
+genshi = ["genshi"]
+lxml = ["lxml"]
+
[[package]]
name = "identify"
version = "2.5.1"
@@ -556,6 +599,55 @@ category = "dev"
optional = false
python-versions = ">=3.6"
+[[package]]
+name = "limits"
+version = "2.6.3"
+description = "Rate limiting utilities"
+category = "main"
+optional = false
+python-versions = ">=3.7"
+
+[package.dependencies]
+deprecated = ">=1.2"
+packaging = ">=21,<22"
+typing-extensions = "*"
+
+[package.extras]
+all = ["redis (>3,<5.0.0)", "redis (>=4.2.0)", "pymemcache (>3,<4.0.0)", "pymongo (>3,<5)", "motor (>=2.5,<4)", "emcache (>=0.6.1)", "coredis[hiredis] (>=3.4.0,<4)"]
+async-memcached = ["emcache (>=0.6.1)"]
+async-mongodb = ["motor (>=2.5,<4)"]
+async-redis = ["coredis[hiredis] (>=3.4.0,<4)"]
+memcached = ["pymemcache (>3,<4.0.0)"]
+mongodb = ["pymongo (>3,<5)"]
+redis = ["redis (>3,<5.0.0)"]
+rediscluster = ["redis (>=4.2.0)"]
+
+[[package]]
+name = "lxml"
+version = "4.9.0"
+description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*"
+
+[package.extras]
+cssselect = ["cssselect (>=0.7)"]
+html5 = ["html5lib"]
+htmlsoup = ["beautifulsoup4"]
+source = ["Cython (>=0.29.7)"]
+
+[[package]]
+name = "m2r2"
+version = "0.3.2"
+description = "Markdown and reStructuredText in a single file."
+category = "dev"
+optional = false
+python-versions = "*"
+
+[package.dependencies]
+docutils = "*"
+mistune = "0.8.4"
+
[[package]]
name = "markupsafe"
version = "2.1.1"
@@ -583,6 +675,22 @@ category = "dev"
optional = false
python-versions = "*"
+[[package]]
+name = "mistune"
+version = "0.8.4"
+description = "The fastest markdown parser in pure Python"
+category = "dev"
+optional = false
+python-versions = "*"
+
+[[package]]
+name = "more-itertools"
+version = "8.13.0"
+description = "More routines for operating on iterables, beyond itertools"
+category = "main"
+optional = false
+python-versions = ">=3.5"
+
[[package]]
name = "multidict"
version = "6.0.2"
@@ -650,7 +758,7 @@ python-versions = "*"
[[package]]
name = "numpy"
-version = "1.22.4"
+version = "1.23.0"
description = "NumPy is the fundamental package for array computing with Python."
category = "main"
optional = false
@@ -669,7 +777,7 @@ pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]]
name = "pandas"
-version = "1.4.2"
+version = "1.4.3"
description = "Powerful data structures for data analysis, time series, and statistics"
category = "main"
optional = false
@@ -1378,6 +1486,23 @@ category = "dev"
optional = false
python-versions = ">= 3.5"
+[[package]]
+name = "tqdm"
+version = "4.64.0"
+description = "Fast, Extensible Progress Meter"
+category = "main"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7"
+
+[package.dependencies]
+colorama = {version = "*", markers = "platform_system == \"Windows\""}
+
+[package.extras]
+dev = ["py-make (>=0.1.0)", "twine", "wheel"]
+notebook = ["ipywidgets (>=6)"]
+slack = ["slack-sdk"]
+telegram = ["requests"]
+
[[package]]
name = "traitlets"
version = "5.3.0"
@@ -1486,11 +1611,19 @@ category = "dev"
optional = false
python-versions = "*"
+[[package]]
+name = "webencodings"
+version = "0.5.1"
+description = "Character encoding aliases for legacy web content"
+category = "main"
+optional = false
+python-versions = "*"
+
[[package]]
name = "wrapt"
version = "1.14.1"
description = "Module for decorators, wrappers and monkey patching."
-category = "dev"
+category = "main"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
@@ -1539,15 +1672,10 @@ python-versions = ">=3.7"
docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"]
testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"]
-[extras]
-docs = []
-linting = []
-testing = []
-
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "49153b6db9d5d283789d18620bd923f068ffb6515a04deae0e267c028c0307c3"
+content-hash = "6da3ad3b613a72a857e3cee9f78a172043835e24518cc586802fe94000f420f0"
[metadata.files]
alabaster = [
@@ -1733,6 +1861,10 @@ decorator = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
+deprecated = [
+ {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"},
+ {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"},
+]
dill = [
{file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"},
{file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"},
@@ -1749,6 +1881,10 @@ dodgy = [
{file = "dodgy-0.2.1-py3-none-any.whl", hash = "sha256:51f54c0fd886fa3854387f354b19f429d38c04f984f38bc572558b703c0542a6"},
{file = "dodgy-0.2.1.tar.gz", hash = "sha256:28323cbfc9352139fdd3d316fa17f325cc0e9ac74438cbba51d70f9b48f86c3a"},
]
+dunamai = [
+ {file = "dunamai-1.12.0-py3-none-any.whl", hash = "sha256:00b9c1ef58d4950204f76c20f84afe7a28d095f77feaa8512dbb172035415e61"},
+ {file = "dunamai-1.12.0.tar.gz", hash = "sha256:fac4f09e2b8a105bd01f8c50450fea5aa489a6c439c949950a65f0dd388b0d20"},
+]
entrypoints = [
{file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"},
{file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"},
@@ -1794,6 +1930,10 @@ geopandas = [
{file = "geopandas-0.10.2-py2.py3-none-any.whl", hash = "sha256:1722853464441b603d9be3d35baf8bde43831424a891e82a8545eb8997b65d6c"},
{file = "geopandas-0.10.2.tar.gz", hash = "sha256:efbf47e70732e25c3727222019c92b39b2e0a66ebe4fe379fbe1aa43a2a871db"},
]
+html5lib = [
+ {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"},
+ {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"},
+]
identify = [
{file = "identify-2.5.1-py2.py3-none-any.whl", hash = "sha256:0dca2ea3e4381c435ef9c33ba100a78a9b40c0bab11189c7cf121f75815efeaa"},
{file = "identify-2.5.1.tar.gz", hash = "sha256:3d11b16f3fe19f52039fb7e39c9c884b21cb1b586988114fbe42671f03de3e82"},
@@ -1881,6 +2021,79 @@ lazy-object-proxy = [
{file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"},
{file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"},
]
+limits = [
+ {file = "limits-2.6.3-py3-none-any.whl", hash = "sha256:cd7e61b179f40fa99d4b76f1640eafab656237d912a932978a9c121910cd4a67"},
+ {file = "limits-2.6.3.tar.gz", hash = "sha256:d32df9de06dfce0f74ab247e183cc57bb6984cd47a92b55739258b161b7701f5"},
+]
+lxml = [
+ {file = "lxml-4.9.0-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b5031d151d6147eac53366d6ec87da84cd4d8c5e80b1d9948a667a7164116e39"},
+ {file = "lxml-4.9.0-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d52e1173f52020392f593f87a6af2d4055dd800574a5cb0af4ea3878801d307"},
+ {file = "lxml-4.9.0-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3af00ee88376022589ceeb8170eb67dacf5f7cd625ea59fa0977d719777d4ae8"},
+ {file = "lxml-4.9.0-cp27-cp27m-win32.whl", hash = "sha256:1057356b808d149bc14eb8f37bb89129f237df488661c1e0fc0376ca90e1d2c3"},
+ {file = "lxml-4.9.0-cp27-cp27m-win_amd64.whl", hash = "sha256:f6d23a01921b741774f35e924d418a43cf03eca1444f3fdfd7978d35a5aaab8b"},
+ {file = "lxml-4.9.0-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:56e19fb6e4b8bd07fb20028d03d3bc67bcc0621347fbde64f248e44839771756"},
+ {file = "lxml-4.9.0-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:4cd69bca464e892ea4ed544ba6a7850aaff6f8d792f8055a10638db60acbac18"},
+ {file = "lxml-4.9.0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:94b181dd2777890139e49a5336bf3a9a3378ce66132c665fe8db4e8b7683cde2"},
+ {file = "lxml-4.9.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:607224ffae9a0cf0a2f6e14f5f6bce43e83a6fbdaa647891729c103bdd6a5593"},
+ {file = "lxml-4.9.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:11d62c97ceff9bab94b6b29c010ea5fb6831743459bb759c917f49ba75601cd0"},
+ {file = "lxml-4.9.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:70a198030d26f5e569367f0f04509b63256faa76a22886280eea69a4f535dd40"},
+ {file = "lxml-4.9.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3cf816aed8125cfc9e6e5c6c31ff94278320d591bd7970c4a0233bee0d1c8790"},
+ {file = "lxml-4.9.0-cp310-cp310-win32.whl", hash = "sha256:65b3b5f12c6fb5611e79157214f3cd533083f9b058bf2fc8a1c5cc5ee40fdc5a"},
+ {file = "lxml-4.9.0-cp310-cp310-win_amd64.whl", hash = "sha256:0aa4cce579512c33373ca4c5e23c21e40c1aa1a33533a75e51b654834fd0e4f2"},
+ {file = "lxml-4.9.0-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:63419db39df8dc5564f6f103102c4665f7e4d9cb64030e98cf7a74eae5d5760d"},
+ {file = "lxml-4.9.0-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d8e5021e770b0a3084c30dda5901d5fce6d4474feaf0ced8f8e5a82702502fbb"},
+ {file = "lxml-4.9.0-cp35-cp35m-win32.whl", hash = "sha256:f17b9df97c5ecdfb56c5e85b3c9df9831246df698f8581c6e111ac664c7c656e"},
+ {file = "lxml-4.9.0-cp35-cp35m-win_amd64.whl", hash = "sha256:75da29a0752c8f2395df0115ac1681cefbdd4418676015be8178b733704cbff2"},
+ {file = "lxml-4.9.0-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:e4d020ecf3740b7312bacab2cb966bb720fd4d3490562d373b4ad91dd1857c0d"},
+ {file = "lxml-4.9.0-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:b71c52d69b91af7d18c13aef1b0cc3baee36b78607c711eb14a52bf3aa7c815e"},
+ {file = "lxml-4.9.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28cf04a1a38e961d4a764d2940af9b941b66263ed5584392ef875ee9c1e360a3"},
+ {file = "lxml-4.9.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:915ecf7d486df17cc65aeefdb680d5ad4390cc8c857cf8db3fe241ed234f856a"},
+ {file = "lxml-4.9.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e564d5a771b4015f34166a05ea2165b7e283635c41b1347696117f780084b46d"},
+ {file = "lxml-4.9.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c2a57755e366e0ac7ebdb3e9207f159c3bf1afed02392ab18453ce81f5ee92ee"},
+ {file = "lxml-4.9.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:00f3a6f88fd5f4357844dd91a1abac5f466c6799f1b7f1da2df6665253845b11"},
+ {file = "lxml-4.9.0-cp36-cp36m-win32.whl", hash = "sha256:9093a359a86650a3dbd6532c3e4d21a6f58ba2cb60d0e72db0848115d24c10ba"},
+ {file = "lxml-4.9.0-cp36-cp36m-win_amd64.whl", hash = "sha256:d1690c4d37674a5f0cdafbc5ed7e360800afcf06928c2a024c779c046891bf09"},
+ {file = "lxml-4.9.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:6af7f51a6010748fc1bb71917318d953c9673e4ae3f6d285aaf93ef5b2eb11c1"},
+ {file = "lxml-4.9.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:eabdbe04ee0a7e760fa6cd9e799d2b020d098c580ba99107d52e1e5e538b1ecb"},
+ {file = "lxml-4.9.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b1e22f3ee4d75ca261b6bffbf64f6f178cb194b1be3191065a09f8d98828daa9"},
+ {file = "lxml-4.9.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:53b0410b220766321759f7f9066da67b1d0d4a7f6636a477984cbb1d98483955"},
+ {file = "lxml-4.9.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d76da27f5e3e9bc40eba6ed7a9e985f57547e98cf20521d91215707f2fb57e0f"},
+ {file = "lxml-4.9.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:686565ac77ff94a8965c11829af253d9e2ce3bf0d9225b1d2eb5c4d4666d0dca"},
+ {file = "lxml-4.9.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b62d1431b4c40cda43cc986f19b8c86b1d2ae8918cfc00f4776fdf070b65c0c4"},
+ {file = "lxml-4.9.0-cp37-cp37m-win32.whl", hash = "sha256:4becd16750ca5c2a1b1588269322b2cebd10c07738f336c922b658dbab96a61c"},
+ {file = "lxml-4.9.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e35a298691b9e10e5a5631f8f0ba605b30ebe19208dc8f58b670462f53753641"},
+ {file = "lxml-4.9.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:aa7447bf7c1a15ef24e2b86a277b585dd3f055e8890ac7f97374d170187daa97"},
+ {file = "lxml-4.9.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:612ef8f2795a89ba3a1d4c8c1af84d8453fd53ee611aa5ad460fdd2cab426fc2"},
+ {file = "lxml-4.9.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:1bfb791a8fcdbf55d1d41b8be940393687bec0e9b12733f0796668086d1a23ff"},
+ {file = "lxml-4.9.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:024684e0c5cfa121c22140d3a0898a3a9b2ea0f0fd2c229b6658af4bdf1155e5"},
+ {file = "lxml-4.9.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81c29c8741fa07ecec8ec7417c3d8d1e2f18cf5a10a280f4e1c3f8c3590228b2"},
+ {file = "lxml-4.9.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6467626fa74f96f4d80fc6ec2555799e97fff8f36e0bfc7f67769f83e59cff40"},
+ {file = "lxml-4.9.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:9cae837b988f44925d14d048fa6a8c54f197c8b1223fd9ee9c27084f84606143"},
+ {file = "lxml-4.9.0-cp38-cp38-win32.whl", hash = "sha256:5a49ad78543925e1a4196e20c9c54492afa4f1502c2a563f73097e2044c75190"},
+ {file = "lxml-4.9.0-cp38-cp38-win_amd64.whl", hash = "sha256:bb7c1b029e54e26e01b1d1d912fc21abb65650d16ea9a191d026def4ed0859ed"},
+ {file = "lxml-4.9.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d0d03b9636f1326772e6854459728676354d4c7731dae9902b180e2065ba3da6"},
+ {file = "lxml-4.9.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:9af19eb789d674b59a9bee5005779757aab857c40bf9cc313cb01eafac55ce55"},
+ {file = "lxml-4.9.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:dd00d28d1ab5fa7627f5abc957f29a6338a7395b724571a8cbff8fbed83aaa82"},
+ {file = "lxml-4.9.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:754a1dd04bff8a509a31146bd8f3a5dc8191a8694d582dd5fb71ff09f0722c22"},
+ {file = "lxml-4.9.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7679344f2270840dc5babc9ccbedbc04f7473c1f66d4676bb01680c0db85bcc"},
+ {file = "lxml-4.9.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:d882c2f3345261e898b9f604be76b61c901fbfa4ac32e3f51d5dc1edc89da3cb"},
+ {file = "lxml-4.9.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e97c8fc761ad63909198acc892f34c20f37f3baa2c50a62d5ec5d7f1efc68a1"},
+ {file = "lxml-4.9.0-cp39-cp39-win32.whl", hash = "sha256:cf9ec915857d260511399ab87e1e70fa13d6b2972258f8e620a3959468edfc32"},
+ {file = "lxml-4.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:1254a79f8a67a3908de725caf59eae62d86738f6387b0a34b32e02abd6ae73db"},
+ {file = "lxml-4.9.0-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:03370ec37fe562238d385e2c53089076dee53aabf8325cab964fdb04a9130fa0"},
+ {file = "lxml-4.9.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f386def57742aacc3d864169dfce644a8c396f95aa35b41b69df53f558d56dd0"},
+ {file = "lxml-4.9.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea3f2e9eb41f973f73619e88bf7bd950b16b4c2ce73d15f24a11800ce1eaf276"},
+ {file = "lxml-4.9.0-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:2d10659e6e5c53298e6d718fd126e793285bff904bb71d7239a17218f6a197b7"},
+ {file = "lxml-4.9.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:fcdf70191f0d1761d190a436db06a46f05af60e1410e1507935f0332280c9268"},
+ {file = "lxml-4.9.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:2b9c2341d96926b0d0e132e5c49ef85eb53fa92ae1c3a70f9072f3db0d32bc07"},
+ {file = "lxml-4.9.0-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:615886ee84b6f42f1bdf1852a9669b5fe3b96b6ff27f1a7a330b67ad9911200a"},
+ {file = "lxml-4.9.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:94f2e45b054dd759bed137b6e14ae8625495f7d90ddd23cf62c7a68f72b62656"},
+ {file = "lxml-4.9.0.tar.gz", hash = "sha256:520461c36727268a989790aef08884347cd41f2d8ae855489ccf40b50321d8d7"},
+]
+m2r2 = [
+ {file = "m2r2-0.3.2-py3-none-any.whl", hash = "sha256:d3684086b61b4bebe2307f15189495360f05a123c9bda2a66462649b7ca236aa"},
+ {file = "m2r2-0.3.2.tar.gz", hash = "sha256:ccd95b052dcd1ac7442ecb3111262b2001c10e4119b459c34c93ac7a5c2c7868"},
+]
markupsafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
@@ -1931,6 +2144,14 @@ mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
+mistune = [
+ {file = "mistune-0.8.4-py2.py3-none-any.whl", hash = "sha256:88a1051873018da288eee8538d476dffe1262495144b33ecb586c4ab266bb8d4"},
+ {file = "mistune-0.8.4.tar.gz", hash = "sha256:59a3429db53c50b5c6bcc8a07f8848cb00d7dc8bdb431a4ab41920d201d4756e"},
+]
+more-itertools = [
+ {file = "more-itertools-8.13.0.tar.gz", hash = "sha256:a42901a0a5b169d925f6f217cd5a190e32ef54360905b9c39ee7db5313bfec0f"},
+ {file = "more_itertools-8.13.0-py3-none-any.whl", hash = "sha256:c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb"},
+]
multidict = [
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"},
{file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"},
@@ -2034,55 +2255,55 @@ nodeenv = [
{file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"},
]
numpy = [
- {file = "numpy-1.22.4-cp310-cp310-macosx_10_14_x86_64.whl", hash = "sha256:ba9ead61dfb5d971d77b6c131a9dbee62294a932bf6a356e48c75ae684e635b3"},
- {file = "numpy-1.22.4-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:1ce7ab2053e36c0a71e7a13a7475bd3b1f54750b4b433adc96313e127b870887"},
- {file = "numpy-1.22.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7228ad13744f63575b3a972d7ee4fd61815b2879998e70930d4ccf9ec721dce0"},
- {file = "numpy-1.22.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43a8ca7391b626b4c4fe20aefe79fec683279e31e7c79716863b4b25021e0e74"},
- {file = "numpy-1.22.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a911e317e8c826ea632205e63ed8507e0dc877dcdc49744584dfc363df9ca08c"},
- {file = "numpy-1.22.4-cp310-cp310-win32.whl", hash = "sha256:9ce7df0abeabe7fbd8ccbf343dc0db72f68549856b863ae3dd580255d009648e"},
- {file = "numpy-1.22.4-cp310-cp310-win_amd64.whl", hash = "sha256:3e1ffa4748168e1cc8d3cde93f006fe92b5421396221a02f2274aab6ac83b077"},
- {file = "numpy-1.22.4-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:59d55e634968b8f77d3fd674a3cf0b96e85147cd6556ec64ade018f27e9479e1"},
- {file = "numpy-1.22.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c1d937820db6e43bec43e8d016b9b3165dcb42892ea9f106c70fb13d430ffe72"},
- {file = "numpy-1.22.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4c5d5eb2ec8da0b4f50c9a843393971f31f1d60be87e0fb0917a49133d257d6"},
- {file = "numpy-1.22.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64f56fc53a2d18b1924abd15745e30d82a5782b2cab3429aceecc6875bd5add0"},
- {file = "numpy-1.22.4-cp38-cp38-win32.whl", hash = "sha256:fb7a980c81dd932381f8228a426df8aeb70d59bbcda2af075b627bbc50207cba"},
- {file = "numpy-1.22.4-cp38-cp38-win_amd64.whl", hash = "sha256:e96d7f3096a36c8754207ab89d4b3282ba7b49ea140e4973591852c77d09eb76"},
- {file = "numpy-1.22.4-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:4c6036521f11a731ce0648f10c18ae66d7143865f19f7299943c985cdc95afb5"},
- {file = "numpy-1.22.4-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:b89bf9b94b3d624e7bb480344e91f68c1c6c75f026ed6755955117de00917a7c"},
- {file = "numpy-1.22.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d487e06ecbf1dc2f18e7efce82ded4f705f4bd0cd02677ffccfb39e5c284c7e"},
- {file = "numpy-1.22.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3eb268dbd5cfaffd9448113539e44e2dd1c5ca9ce25576f7c04a5453edc26fa"},
- {file = "numpy-1.22.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37431a77ceb9307c28382c9773da9f306435135fae6b80b62a11c53cfedd8802"},
- {file = "numpy-1.22.4-cp39-cp39-win32.whl", hash = "sha256:cc7f00008eb7d3f2489fca6f334ec19ca63e31371be28fd5dad955b16ec285bd"},
- {file = "numpy-1.22.4-cp39-cp39-win_amd64.whl", hash = "sha256:f0725df166cf4785c0bc4cbfb320203182b1ecd30fee6e541c8752a92df6aa32"},
- {file = "numpy-1.22.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0791fbd1e43bf74b3502133207e378901272f3c156c4df4954cad833b1380207"},
- {file = "numpy-1.22.4.zip", hash = "sha256:425b390e4619f58d8526b3dcf656dde069133ae5c240229821f01b5f44ea07af"},
+ {file = "numpy-1.23.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58bfd40eb478f54ff7a5710dd61c8097e169bc36cc68333d00a9bcd8def53b38"},
+ {file = "numpy-1.23.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:196cd074c3f97c4121601790955f915187736f9cf458d3ee1f1b46aff2b1ade0"},
+ {file = "numpy-1.23.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1d88ef79e0a7fa631bb2c3dda1ea46b32b1fe614e10fedd611d3d5398447f2f"},
+ {file = "numpy-1.23.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d54b3b828d618a19779a84c3ad952e96e2c2311b16384e973e671aa5be1f6187"},
+ {file = "numpy-1.23.0-cp310-cp310-win32.whl", hash = "sha256:2b2da66582f3a69c8ce25ed7921dcd8010d05e59ac8d89d126a299be60421171"},
+ {file = "numpy-1.23.0-cp310-cp310-win_amd64.whl", hash = "sha256:97a76604d9b0e79f59baeca16593c711fddb44936e40310f78bfef79ee9a835f"},
+ {file = "numpy-1.23.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d8cc87bed09de55477dba9da370c1679bd534df9baa171dd01accbb09687dac3"},
+ {file = "numpy-1.23.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0f18804df7370571fb65db9b98bf1378172bd4e962482b857e612d1fec0f53e"},
+ {file = "numpy-1.23.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac86f407873b952679f5f9e6c0612687e51547af0e14ddea1eedfcb22466babd"},
+ {file = "numpy-1.23.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae8adff4172692ce56233db04b7ce5792186f179c415c37d539c25de7298d25d"},
+ {file = "numpy-1.23.0-cp38-cp38-win32.whl", hash = "sha256:fe8b9683eb26d2c4d5db32cd29b38fdcf8381324ab48313b5b69088e0e355379"},
+ {file = "numpy-1.23.0-cp38-cp38-win_amd64.whl", hash = "sha256:5043bcd71fcc458dfb8a0fc5509bbc979da0131b9d08e3d5f50fb0bbb36f169a"},
+ {file = "numpy-1.23.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1c29b44905af288b3919803aceb6ec7fec77406d8b08aaa2e8b9e63d0fe2f160"},
+ {file = "numpy-1.23.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:98e8e0d8d69ff4d3fa63e6c61e8cfe2d03c29b16b58dbef1f9baa175bbed7860"},
+ {file = "numpy-1.23.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a506cacf2be3a74ead5467aee97b81fca00c9c4c8b3ba16dbab488cd99ba10"},
+ {file = "numpy-1.23.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:092f5e6025813e64ad6d1b52b519165d08c730d099c114a9247c9bb635a2a450"},
+ {file = "numpy-1.23.0-cp39-cp39-win32.whl", hash = "sha256:d6ca8dabe696c2785d0c8c9b0d8a9b6e5fdbe4f922bde70d57fa1a2848134f95"},
+ {file = "numpy-1.23.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc431493df245f3c627c0c05c2bd134535e7929dbe2e602b80e42bf52ff760bc"},
+ {file = "numpy-1.23.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f9c3fc2adf67762c9fe1849c859942d23f8d3e0bee7b5ed3d4a9c3eeb50a2f07"},
+ {file = "numpy-1.23.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0d2094e8f4d760500394d77b383a1b06d3663e8892cdf5df3c592f55f3bff66"},
+ {file = "numpy-1.23.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:94b170b4fa0168cd6be4becf37cb5b127bd12a795123984385b8cd4aca9857e5"},
+ {file = "numpy-1.23.0.tar.gz", hash = "sha256:bd3fa4fe2e38533d5336e1272fc4e765cabbbde144309ccee8675509d5cd7b05"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
]
pandas = [
- {file = "pandas-1.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be67c782c4f1b1f24c2f16a157e12c2693fd510f8df18e3287c77f33d124ed07"},
- {file = "pandas-1.4.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5a206afa84ed20e07603f50d22b5f0db3fb556486d8c2462d8bc364831a4b417"},
- {file = "pandas-1.4.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0010771bd9223f7afe5f051eb47c4a49534345dfa144f2f5470b27189a4dd3b5"},
- {file = "pandas-1.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3228198333dd13c90b6434ddf61aa6d57deaca98cf7b654f4ad68a2db84f8cfe"},
- {file = "pandas-1.4.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b79af3a69e5175c6fa7b4e046b21a646c8b74e92c6581a9d825687d92071b51"},
- {file = "pandas-1.4.2-cp310-cp310-win_amd64.whl", hash = "sha256:5586cc95692564b441f4747c47c8a9746792e87b40a4680a2feb7794defb1ce3"},
- {file = "pandas-1.4.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:061609334a8182ab500a90fe66d46f6f387de62d3a9cb9aa7e62e3146c712167"},
- {file = "pandas-1.4.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b8134651258bce418cb79c71adeff0a44090c98d955f6953168ba16cc285d9f7"},
- {file = "pandas-1.4.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:df82739e00bb6daf4bba4479a40f38c718b598a84654cbd8bb498fd6b0aa8c16"},
- {file = "pandas-1.4.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:385c52e85aaa8ea6a4c600a9b2821181a51f8be0aee3af6f2dcb41dafc4fc1d0"},
- {file = "pandas-1.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:295872bf1a09758aba199992c3ecde455f01caf32266d50abc1a073e828a7b9d"},
- {file = "pandas-1.4.2-cp38-cp38-win32.whl", hash = "sha256:95c1e422ced0199cf4a34385ff124b69412c4bc912011ce895582bee620dfcaa"},
- {file = "pandas-1.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:5c54ea4ef3823108cd4ec7fb27ccba4c3a775e0f83e39c5e17f5094cb17748bc"},
- {file = "pandas-1.4.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c072c7f06b9242c855ed8021ff970c0e8f8b10b35e2640c657d2a541c5950f59"},
- {file = "pandas-1.4.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f549097993744ff8c41b5e8f2f0d3cbfaabe89b4ae32c8c08ead6cc535b80139"},
- {file = "pandas-1.4.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ff08a14ef21d94cdf18eef7c569d66f2e24e0bc89350bcd7d243dd804e3b5eb2"},
- {file = "pandas-1.4.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c5bf555b6b0075294b73965adaafb39cf71c312e38c5935c93d78f41c19828a"},
- {file = "pandas-1.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51649ef604a945f781105a6d2ecf88db7da0f4868ac5d45c51cb66081c4d9c73"},
- {file = "pandas-1.4.2-cp39-cp39-win32.whl", hash = "sha256:d0d4f13e4be7ce89d7057a786023c461dd9370040bdb5efa0a7fe76b556867a0"},
- {file = "pandas-1.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:09d8be7dd9e1c4c98224c4dfe8abd60d145d934e9fc1f5f411266308ae683e6a"},
- {file = "pandas-1.4.2.tar.gz", hash = "sha256:92bc1fc585f1463ca827b45535957815b7deb218c549b7c18402c322c7549a12"},
+ {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d51674ed8e2551ef7773820ef5dab9322be0828629f2cbf8d1fc31a0c4fed640"},
+ {file = "pandas-1.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:16ad23db55efcc93fa878f7837267973b61ea85d244fc5ff0ccbcfa5638706c5"},
+ {file = "pandas-1.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:958a0588149190c22cdebbc0797e01972950c927a11a900fe6c2296f207b1d6f"},
+ {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e48fbb64165cda451c06a0f9e4c7a16b534fcabd32546d531b3c240ce2844112"},
+ {file = "pandas-1.4.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f803320c9da732cc79210d7e8cc5c8019aad512589c910c66529eb1b1818230"},
+ {file = "pandas-1.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:2893e923472a5e090c2d5e8db83e8f907364ec048572084c7d10ef93546be6d1"},
+ {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:24ea75f47bbd5574675dae21d51779a4948715416413b30614c1e8b480909f81"},
+ {file = "pandas-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ebc990bd34f4ac3c73a2724c2dcc9ee7bf1ce6cf08e87bb25c6ad33507e318"},
+ {file = "pandas-1.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d6c0106415ff1a10c326c49bc5dd9ea8b9897a6ca0c8688eb9c30ddec49535ef"},
+ {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78b00429161ccb0da252229bcda8010b445c4bf924e721265bec5a6e96a92e92"},
+ {file = "pandas-1.4.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dfbf16b1ea4f4d0ee11084d9c026340514d1d30270eaa82a9f1297b6c8ecbf0"},
+ {file = "pandas-1.4.3-cp38-cp38-win32.whl", hash = "sha256:48350592665ea3cbcd07efc8c12ff12d89be09cd47231c7925e3b8afada9d50d"},
+ {file = "pandas-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:605d572126eb4ab2eadf5c59d5d69f0608df2bf7bcad5c5880a47a20a0699e3e"},
+ {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a3924692160e3d847e18702bb048dc38e0e13411d2b503fecb1adf0fcf950ba4"},
+ {file = "pandas-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07238a58d7cbc8a004855ade7b75bbd22c0db4b0ffccc721556bab8a095515f6"},
+ {file = "pandas-1.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:755679c49460bd0d2f837ab99f0a26948e68fa0718b7e42afbabd074d945bf84"},
+ {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41fc406e374590a3d492325b889a2686b31e7a7780bec83db2512988550dadbf"},
+ {file = "pandas-1.4.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d9382f72a4f0e93909feece6fef5500e838ce1c355a581b3d8f259839f2ea76"},
+ {file = "pandas-1.4.3-cp39-cp39-win32.whl", hash = "sha256:0daf876dba6c622154b2e6741f29e87161f844e64f84801554f879d27ba63c0d"},
+ {file = "pandas-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:721a3dd2f06ef942f83a819c0f3f6a648b2830b191a72bbe9451bcd49c3bd42e"},
+ {file = "pandas-1.4.3.tar.gz", hash = "sha256:2ff7788468e75917574f080cd4681b27e1a7bf36461fe968b49a87b5a54d007c"},
]
parso = [
{file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"},
@@ -2558,6 +2779,10 @@ tornado = [
{file = "tornado-6.1-cp39-cp39-win_amd64.whl", hash = "sha256:548430be2740e327b3fe0201abe471f314741efcb0067ec4f2d7dcfb4825f3e4"},
{file = "tornado-6.1.tar.gz", hash = "sha256:33c6e81d7bd55b468d2e793517c909b139960b6c790a60b7991b9b6b76fb9791"},
]
+tqdm = [
+ {file = "tqdm-4.64.0-py2.py3-none-any.whl", hash = "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6"},
+ {file = "tqdm-4.64.0.tar.gz", hash = "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d"},
+]
traitlets = [
{file = "traitlets-5.3.0-py3-none-any.whl", hash = "sha256:65fa18961659635933100db8ca120ef6220555286949774b9cfc106f941d1c7a"},
{file = "traitlets-5.3.0.tar.gz", hash = "sha256:0bb9f1f9f017aa8ec187d8b1b2a7a6626a2a1d877116baba52a129bfa124f8e2"},
@@ -2594,6 +2819,10 @@ wcwidth = [
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
]
+webencodings = [
+ {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"},
+ {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"},
+]
wrapt = [
{file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"},
{file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"},
diff --git a/pyproject.toml b/pyproject.toml
index c8b87b1..c53a1ff 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -6,11 +6,11 @@ authors = [
"Panos Mavrogiorgos ",
"Zachary Burnett ",
]
-license = "EUPL-1.0"
+license = 'GPL-3.0-or-later'
readme = "README.md"
repository = "https://github.com/oceanmodeling/searvey.git"
classifiers = [
- "License :: OSI Approved :: European Union Public Licence 1.2 (EUPL 1.2)",
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Operating System :: OS Independent",
"Development Status :: 4 - Beta",
"Environment :: Other Environment",
@@ -43,6 +43,11 @@ geopandas = "^0.10"
typepigeon = "^1"
xarray = "^2022.3"
Shapely = "^1.8"
+limits = "^2.6"
+tqdm = "^4.64"
+html5lib = "^1.1"
+lxml = "^4.9"
+more-itertools = "^8.0"
[tool.poetry.dev-dependencies]
ipykernel = "*"
@@ -52,15 +57,12 @@ prospector = "^1.5"
pytest = "^7.0"
pytest-xdist = "*"
pytest-cov = "^3.0"
-pytest-recording = "^0.12.0"
+pytest-recording = "^0.12"
types-requests = "^2.26"
sphinx = '*'
sphinx-rtd-theme = '*'
-
-[tool.poetry.extras]
-linting = ["mypy", "prospector"]
-testing = ["pre-commit", "pytest", "pytest-cov", "pytest-recording", "pytest-xdist", "types-requests"]
-docs = ["dunamai", "m2r2", "sphinx", "sphinx-rtd-theme"]
+dunamai = "^1.12"
+m2r2 = "^0.3"
[tool.black]
line-length = 108
@@ -70,6 +72,11 @@ target-version = ['py39']
minversion = "7.0"
addopts = "-ra --verbose --showlocals --tb=short --cov=searvey --cov-report term-missing"
testpaths = ["tests"]
+log_cli = true
+filterwarnings = [
+ 'ignore:distutils Version classes are deprecated. Use packaging.version instead:DeprecationWarning',
+]
+
[tool.mypy]
python_version = "3.9"
diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt
index 55051c0..028ffd3 100644
--- a/requirements/requirements-dev.txt
+++ b/requirements/requirements-dev.txt
@@ -14,14 +14,16 @@ charset-normalizer==2.0.12; python_version >= "3.7" and python_version < "4" and
click-plugins==1.1.1; python_version >= "3.7"
click==8.1.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.7"
cligj==0.7.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.7"
-colorama==0.4.5; sys_platform == "win32" and python_version >= "3.8" and python_full_version >= "3.7.2" and python_version < "4.0" and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") and python_full_version >= "3.5.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0")
+colorama==0.4.5; python_full_version >= "3.7.2" and platform_system == "Windows" and sys_platform == "win32" and python_version >= "3.8" and python_version < "4.0" and (python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.5.0") and (python_version >= "3.6" and python_full_version < "3.0.0" and sys_platform == "win32" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") or sys_platform == "win32" and python_version >= "3.6" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6") and python_full_version >= "3.5.0") and (python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0")
coverage==6.4.1; python_version >= "3.7"
debugpy==1.6.0; python_version >= "3.7"
decorator==5.1.1; python_version >= "3.8"
+deprecated==1.2.13; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
dill==0.3.5.1; python_full_version >= "3.7.2" and python_version < "4.0"
distlib==0.3.4; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
docutils==0.17.1; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
dodgy==0.2.1; python_full_version >= "3.6.2" and python_version < "4.0"
+dunamai==1.12.0; python_version >= "3.5" and python_version < "4.0"
entrypoints==0.4; python_version >= "3.7"
erddapy==1.2.1; python_version >= "3.6"
execnet==1.9.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.6"
@@ -31,6 +33,7 @@ fiona==1.8.21; python_version >= "3.7"
flake8-polyfill==1.0.2; python_full_version >= "3.6.2" and python_version < "4.0"
flake8==4.0.1; python_full_version >= "3.6.2" and python_version < "4.0" and python_version >= "3.6"
geopandas==0.10.2; python_version >= "3.7"
+html5lib==1.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
identify==2.5.1; python_version >= "3.7"
idna==3.3; python_version >= "3.7" and python_version < "4"
imagesize==1.3.0; python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6"
@@ -44,18 +47,23 @@ jinja2==3.1.2; python_version >= "3.7" and python_full_version < "3.0.0" or pyth
jupyter-client==7.3.4; python_version >= "3.7"
jupyter-core==4.10.0; python_version >= "3.7"
lazy-object-proxy==1.7.1; python_full_version >= "3.7.2" and python_version < "4.0" and python_version >= "3.6"
+limits==2.6.3; python_version >= "3.7"
+lxml==4.9.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
+m2r2==0.3.2
markupsafe==2.1.1; python_version >= "3.7"
matplotlib-inline==0.1.3; python_version >= "3.8"
mccabe==0.6.1; python_full_version >= "3.7.2" and python_version < "4.0" and python_version >= "3.6"
+mistune==0.8.4
+more-itertools==8.13.0; python_version >= "3.5"
multidict==6.0.2; python_version >= "3.7"
munch==2.5.0; python_version >= "3.7"
mypy-extensions==0.4.3; python_version >= "3.6"
mypy==0.950; python_version >= "3.6"
nest-asyncio==1.5.5; python_version >= "3.7"
nodeenv==1.6.0; python_version >= "3.7"
-numpy==1.22.4
-packaging==21.3; python_version >= "3.8" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.8"
-pandas==1.4.2; python_version >= "3.8"
+numpy==1.23.0
+packaging==21.3; python_version >= "3.8" and python_version < "4.0" and (python_version >= "3.6" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.6")
+pandas==1.4.3; python_version >= "3.8"
parso==0.8.3; python_version >= "3.8"
pep8-naming==0.10.0; python_full_version >= "3.6.2" and python_version < "4.0"
pexpect==4.8.0; sys_platform != "win32" and python_version >= "3.8"
@@ -80,7 +88,7 @@ pylint-django==2.5.3; python_full_version >= "3.6.2" and python_version < "4.0"
pylint-flask==0.6; python_full_version >= "3.6.2" and python_version < "4.0"
pylint-plugin-utils==0.7; python_full_version >= "3.6.2" and python_version < "4.0"
pylint==2.14.3; python_full_version >= "3.7.2" and python_version < "4.0"
-pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.8"
+pyparsing==3.0.9; python_version >= "3.8" and python_version < "4.0" and python_full_version >= "3.6.8"
pyproj==3.3.1; python_version >= "3.8"
pytest-cov==3.0.0; python_version >= "3.6"
pytest-forked==1.4.0; python_version >= "3.6"
@@ -112,6 +120,7 @@ toml==0.10.2; python_full_version >= "3.6.2" and python_version >= "3.7" and pyt
tomli==2.0.1; python_version < "3.11" and python_version >= "3.7" and python_full_version >= "3.7.2" and python_full_version <= "3.11.0a6"
tomlkit==0.11.0; python_version >= "3.6" and python_version < "4.0" and python_full_version >= "3.7.2"
tornado==6.1; python_version >= "3.7"
+tqdm==4.64.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
traitlets==5.3.0; python_version >= "3.8"
typepigeon==1.0.14; python_version >= "3.6" and python_version < "4.0"
types-requests==2.27.31
@@ -121,7 +130,8 @@ urllib3==1.26.9; python_version >= "3.7" and python_full_version < "3.0.0" and p
vcrpy==4.1.1; python_version >= "3.5"
virtualenv==20.14.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
wcwidth==0.2.5; python_full_version >= "3.6.2" and python_version >= "3.8"
-wrapt==1.14.1; python_full_version >= "3.7.2" and python_version >= "3.5" and python_version < "4.0"
+webencodings==0.5.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
+wrapt==1.14.1; python_full_version >= "3.7.2" and python_version >= "3.7" and python_version < "4.0"
xarray==2022.3.0; python_version >= "3.8"
yarl==1.7.2; python_version >= "3.6"
zipp==3.8.0; python_version < "3.10" and python_version >= "3.7"
diff --git a/requirements/requirements.txt b/requirements/requirements.txt
index ff1afd5..4806cd2 100644
--- a/requirements/requirements.txt
+++ b/requirements/requirements.txt
@@ -5,15 +5,20 @@ charset-normalizer==2.0.12; python_version >= "3.7" and python_version < "4" and
click-plugins==1.1.1; python_version >= "3.7"
click==8.1.3; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.7"
cligj==0.7.2; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.3.0" and python_version < "4" and python_version >= "3.7"
-colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or platform_system == "Windows" and python_version >= "3.7" and python_full_version >= "3.5.0"
+colorama==0.4.5; python_version >= "3.7" and python_full_version < "3.0.0" and platform_system == "Windows" or python_full_version >= "3.5.0" and platform_system == "Windows" and python_version >= "3.7"
+deprecated==1.2.13; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.4.0" and python_version >= "3.7"
erddapy==1.2.1; python_version >= "3.6"
fiona==1.8.21; python_version >= "3.7"
geopandas==0.10.2; python_version >= "3.7"
+html5lib==1.1; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
idna==3.3; python_version >= "3.7" and python_version < "4"
+limits==2.6.3; python_version >= "3.7"
+lxml==4.9.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.5.0")
+more-itertools==8.13.0; python_version >= "3.5"
munch==2.5.0; python_version >= "3.7"
-numpy==1.22.4
+numpy==1.23.0
packaging==21.3; python_version >= "3.8"
-pandas==1.4.2; python_version >= "3.8"
+pandas==1.4.3; python_version >= "3.8"
pydantic==1.9.1; python_full_version >= "3.6.1"
pyparsing==3.0.9; python_full_version >= "3.6.8" and python_version >= "3.8"
pyproj==3.3.1; python_version >= "3.8"
@@ -21,9 +26,12 @@ python-dateutil==2.8.2; python_version >= "3.8" and python_full_version < "3.0.0
pytz==2022.1; python_version >= "3.8"
requests==2.28.0; python_version >= "3.7" and python_version < "4"
shapely==1.8.2; python_version >= "3.6"
-six==1.16.0; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.3.0" and python_version >= "3.8" and python_version < "4.0"
+six==1.16.0; python_version >= "3.8" and python_full_version < "3.0.0" and python_version < "4.0" or python_full_version >= "3.5.0" and python_version >= "3.8" and python_version < "4.0"
soupsieve==2.3.2.post1; python_version >= "3.6" and python_full_version >= "3.6.0"
+tqdm==4.64.0; (python_version >= "2.7" and python_full_version < "3.0.0") or (python_full_version >= "3.4.0")
typepigeon==1.0.14; python_version >= "3.6" and python_version < "4.0"
typing-extensions==4.2.0; python_version >= "3.7" and python_full_version >= "3.6.1"
urllib3==1.26.9; python_version >= "3.7" and python_full_version < "3.0.0" and python_version < "4" or python_full_version >= "3.5.0" and python_version < "4" and python_version >= "3.7"
+webencodings==0.5.1; python_version >= "2.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0"
+wrapt==1.14.1; python_version >= "3.7" and python_full_version < "3.0.0" or python_full_version >= "3.5.0" and python_version >= "3.7"
xarray==2022.3.0; python_version >= "3.8"
diff --git a/searvey/coops.py b/searvey/coops.py
index f71b90e..e591392 100644
--- a/searvey/coops.py
+++ b/searvey/coops.py
@@ -2,8 +2,10 @@
interface with the U.S. National Oceanic and Atmospheric Administration (NOAA) Center for Operational Oceanographic Products and Services (CO-OPS) API
https://api.tidesandcurrents.noaa.gov/api/prod/
"""
-# pylint: disable=unused-private-member
import json
+import logging
+from abc import ABC
+from abc import abstractmethod
from datetime import datetime
from enum import Enum
from functools import lru_cache
@@ -25,15 +27,111 @@
from pandas import DataFrame
from pandas import Series
from shapely.geometry import MultiPolygon
+from shapely.geometry import Point
from shapely.geometry import Polygon
from xarray import Dataset
-from searvey.station import Station
-from searvey.station import StationDataInterval
-from searvey.station import StationDataProduct
-from searvey.station import StationDatum
-from searvey.station import StationQuery
-from searvey.station import StationStatus
+
+logger = logging.getLogger(__name__)
+
+
+class StationDataProduct(Enum):
+ pass
+
+
+class StationDataInterval(Enum):
+ pass
+
+
+class StationDatum(Enum):
+ pass
+
+
+class StationStatus(Enum):
+ ACTIVE = "active"
+ DISCONTINUED = "discontinued"
+
+
+class Station(ABC):
+ """
+ abstraction of a specific data station
+ """
+
+ id: str
+ location: Point
+
+ def __init__(self, id: str, location: Point):
+ self.id = id
+ self.location = location
+
+ @abstractmethod
+ def product(
+ self,
+ product: StationDataProduct,
+ start_date: datetime,
+ end_date: datetime = None,
+ interval: StationDataInterval = None,
+ datum: StationDatum = None,
+ ) -> Dataset:
+ """
+ retrieve data for the current station within the specified parameters
+
+ :param product: name of data product
+ :param start_date: start date
+ :param end_date: end date
+ :param interval: time interval of data
+ :param datum: vertical datum
+ :return: data for the current station within the specified parameters
+ """
+ raise NotImplementedError()
+
+ def __str__(self) -> str:
+ return f'{self.__class__.__name__} - "{self.id}" {self.location}'
+
+
+class StationQuery(ABC):
+ """
+ abstraction of an individual station data query
+ """
+
+ station_id: str
+ product: StationDataProduct
+ start_date: datetime
+ end_date: datetime
+ interval: StationDataInterval
+ datum: StationDatum
+
+ def __init__(
+ self,
+ station_id: str,
+ product: StationDataProduct,
+ start_date: datetime,
+ end_date: datetime = None,
+ interval: StationDataInterval = None,
+ datum: StationDatum = None,
+ ):
+ self.station_id = station_id
+ self.product = product
+ self.start_date = start_date
+ self.end_date = end_date
+ self.interval = interval
+ self.datum = datum
+
+ @property
+ @abstractmethod
+ def query(self) -> Dict[str, Any]:
+ raise NotImplementedError()
+
+ @property
+ @abstractmethod
+ def data(self) -> DataFrame:
+ """
+ :return: data for the current query parameters
+ """
+ raise NotImplementedError()
+
+ def __str__(self) -> str:
+ return f'{self.__class__.__name__} - {self.product} at station "{self.station_id}" between {self.start_date} and {self.end_date} over {self.interval} in {self.datum}'
class COOPS_Product(StationDataProduct): # noqa: N801
@@ -109,7 +207,7 @@ class COOPS_Station(Station): # noqa: N801
a specific CO-OPS station
"""
- def __init__(self, id: Union[int, str]): # pylint: disable=redefined-builtin
+ def __init__(self, id: Union[int, str]):
"""
:param id: NOS ID, NWS ID, or station name
@@ -237,6 +335,7 @@ def product(
q (nos_id, t) object 'v' 'v' 'v' 'v' 'v' 'v' ... 'v' 'v' 'v' 'v' 'v'
"""
+ logger.debug("Downloading: %s", self)
if self.__query is None:
self.__query = COOPS_Query(
station=int(self.id),
@@ -495,15 +594,15 @@ def __repr__(self) -> str:
@lru_cache(maxsize=1)
def __coops_stations_html_tables() -> element.ResultSet:
- response = requests.get(
- "https://access.co-ops.nos.noaa.gov/nwsproducts.html?type=current",
- )
+ url = "https://access.co-ops.nos.noaa.gov/nwsproducts.html?type=current"
+ logger.debug("Downloading: %s", url)
+ response = requests.get(url)
soup = BeautifulSoup(response.content, features="html.parser")
return soup.find_all("div", {"class": "table-responsive"})
@lru_cache(maxsize=1)
-def coops_stations(station_status: StationStatus = None) -> GeoDataFrame: # pylint: disable=too-many-locals
+def coops_stations(station_status: StationStatus = None) -> GeoDataFrame:
"""
retrieve a list of CO-OPS stations with associated metadata
@@ -524,8 +623,8 @@ def coops_stations(station_status: StationStatus = None) -> GeoDataFrame: # pyl
9439011 HMDO3 Hammond OR discontinued 2014-08-13 00:00:00,2011-04-12 23:59:00,2011-0... POINT (-123.93750 46.18750)
8762372 LABL1 East Bank 1, Norco, B. LaBranche LA discontinued 2012-11-05 10:38:00,2012-11-05 10:37:00,2012-1... POINT (-90.37500 30.04688)
8530528 CARN4 CARLSTADT, HACKENSACK RIVER NJ discontinued 1994-11-12 23:59:00,1994-11-12 00:00:00 POINT (-74.06250 40.81250)
- [433 rows x 6 columns]
- >>> coops_stations(station_status='ACTIVE')
+ [436 rows x 6 columns]
+ >>> coops_stations(station_status='active')
nws_id name state status removed geometry
nos_id
1600012 46125 QREB buoy active POINT (122.62500 37.75000)
@@ -539,22 +638,22 @@ def coops_stations(station_status: StationStatus = None) -> GeoDataFrame: # pyl
9761115 BARA9 Barbuda active POINT (-61.81250 17.59375)
9999530 FRCB6 Bermuda, Ferry Reach Channel active POINT (-64.68750 32.37500)
9999531 Calcasieu Test Station LA active POINT (-93.31250 29.76562)
- [363 rows x 6 columns]
- >>> coops_stations(station_status='DISCONTINUED')
- nws_id name state status removed geometry
+ [365 rows x 6 columns]
+ >>> coops_stations(station_status='discontinued')
+ nws_id name state status removed geometry
nos_id
- 8516945 KPTN6 Kings Point NY active 2022-02-23 10:15:00,2018-03-20 13:00:00,2015-1... POINT (-73.75000 40.81250)
- 8720357 BKBF1 I-295 Buckman Bridge FL active 2022-02-04 17:00:00,2021-02-20 14:45:00,2021-0... POINT (-81.68750 30.18750)
- 8720226 MSBF1 Southbank Riverwalk, St Johns River FL active 2022-02-02 14:00:00,2021-01-16 17:57:00,2020-0... POINT (-81.68750 30.31250)
- 9063079 MRHO1 Marblehead OH active 2022-02-01 00:00:00,2021-07-15 00:00:00,2019-0... POINT (-82.75000 41.53125)
- 8724580 KYWF1 Key West FL active 2022-01-31 00:00:00,2020-05-08 09:30:00,2018-0... POINT (-81.81250 24.54688)
- ... ... ... ... ... ... ...
- 8760551 SPSL1 South Pass LA discontinued 2000-09-26 23:59:00,2000-09-26 00:00:00,1998-1... POINT (-89.12500 28.98438)
- 9440572 ILWW1 JETTY A, COLUMBIA RIVER WA discontinued 1997-04-11 23:00:00 POINT (-124.06250 46.28125)
- 9415316 RVXC1 Rio Vista CA discontinued 1997-03-04 23:59:00,1997-03-04 00:00:00 POINT (-121.68750 38.15625)
- 9415064 NCHC1 ANTIOCH, SAN JOAQUIN RIVER CA discontinued 1997-03-03 23:59:00,1997-03-03 00:00:00 POINT (-121.81250 38.03125)
- 8530528 CARN4 CARLSTADT, HACKENSACK RIVER NJ discontinued 1994-11-12 23:59:00,1994-11-12 00:00:00 POINT (-74.06250 40.81250)
- [396 rows x 6 columns]
+ 8530528 CARN4 CARLSTADT, HACKENSACK RIVER NJ discontinued 1994-11-12 23:59:00,1994-11-12 00:00:00 POINT (-74.06250 40.81250)
+ 9415064 NCHC1 ANTIOCH, SAN JOAQUIN RIVER CA discontinued 1997-03-03 23:59:00,1997-03-03 00:00:00 POINT (-121.81250 38.03125)
+ 9415316 RVXC1 Rio Vista CA discontinued 1997-03-04 23:59:00,1997-03-04 00:00:00 POINT (-121.68750 38.15625)
+ 9440572 ILWW1 JETTY A, COLUMBIA RIVER WA discontinued 1997-04-11 23:00:00 POINT (-124.06250 46.28125)
+ 8760551 SPSL1 South Pass LA discontinued 2000-09-26 23:59:00,2000-09-26 00:00:00,1998-1... POINT (-89.12500 28.98438)
+ ... ... ... ... ... ... ...
+ 8726667 MCYF1 Mckay Bay Entrance FL discontinued 2020-05-20 00:00:00,2019-03-08 00:00:00,2017-0... POINT (-82.43750 27.90625)
+ 8772447 FCGT2 Freeport TX discontinued 2020-05-24 18:45:00,2018-10-10 21:50:00,2018-1... POINT (-95.31250 28.93750)
+ 9087079 GBWW3 Green Bay WI discontinued 2020-10-28 13:00:00,2007-08-06 23:59:00,2007-0... POINT (-88.00000 44.53125)
+ 8770570 SBPT2 Sabine Pass North TX discontinued 2021-01-18 00:00:00,2020-09-30 15:45:00,2020-0... POINT (-93.87500 29.73438)
+ 8740166 GBRM6 Grand Bay NERR, Mississippi Sound MS discontinued 2022-04-07 00:00:00,2022-03-30 23:58:00,2015-1... POINT (-88.37500 30.40625)
+ [71 rows x 6 columns]
"""
tables = __coops_stations_html_tables()
@@ -566,7 +665,7 @@ def coops_stations(station_status: StationStatus = None) -> GeoDataFrame: # pyl
dataframes = {}
for status, (table_index, table_id) in status_tables.items():
- table = tables[table_index] # pylint: disable=invalid-sequence-index
+ table = tables[table_index]
table = table.find("table", {"id": table_id}).find_all("tr")
stations_columns = [field.text for field in table[0].find_all("th")]
stations = DataFrame(
diff --git a/searvey/critech.py b/searvey/critech.py
index 32a7f13..5757aed 100644
--- a/searvey/critech.py
+++ b/searvey/critech.py
@@ -10,6 +10,7 @@
from searvey.erddap import query_erddap
from searvey.models import ERDDAPDataset
+from searvey.models import SymmetricBBox
from searvey.models import SymmetricConstraints
_CRITECH_NORMALIZED_NAMES = {
@@ -68,14 +69,17 @@ def get_critech_data(
lon_max: float = 180,
timeout: int = 60,
) -> pd.DataFrame:
- constraints = SymmetricConstraints(
- start_date=start_date,
- end_date=end_date or datetime.datetime.now(),
+ bbox = SymmetricBBox(
lat_min=lat_min,
lat_max=lat_max,
lon_min=lon_min,
lon_max=lon_max,
)
+ constraints = SymmetricConstraints(
+ bbox=bbox,
+ start_date=start_date,
+ end_date=end_date or datetime.datetime.now(),
+ )
df = query_erddap(dataset=EMODNET_CRITECH, constraints=constraints, timeout=timeout)
df = normalize_names(df)
df = remove_null_sea_levels(df)
diff --git a/searvey/erddap.py b/searvey/erddap.py
index 022d270..6e788e9 100644
--- a/searvey/erddap.py
+++ b/searvey/erddap.py
@@ -56,10 +56,10 @@ def get_erddap_url(
erddap.constraints = {
"time>=": ts_to_erddap(constraints.start_date),
"time<=": ts_to_erddap(constraints.end_date),
- "latitude>=": constraints.lat_min,
- "latitude<=": constraints.lat_max,
- "longitude>=": constraints.lon_min,
- "longitude<=": constraints.lon_max,
+ "latitude>=": constraints.bbox.lat_min,
+ "latitude<=": constraints.bbox.lat_max,
+ "longitude>=": constraints.bbox.lon_min,
+ "longitude<=": constraints.bbox.lon_max,
}
url = erddap.get_download_url()
return cast(str, url)
diff --git a/searvey/ioc.py b/searvey/ioc.py
new file mode 100644
index 0000000..040984f
--- /dev/null
+++ b/searvey/ioc.py
@@ -0,0 +1,332 @@
+"""
+For the IOC stations we parse their website_.
+
+:: _website: http://www.ioc-sealevelmonitoring.org/list.php?showall=all
+
+This page contains 3 tables:
+
+- ``general``
+- ``contacts``
+- ``performance``
+
+We parse all 3 of them and we merge them.
+"""
+from __future__ import annotations
+
+import datetime
+import functools
+import logging
+from typing import Optional
+from typing import Union
+
+import bs4
+import geopandas as gpd
+import html5lib # noqa: F401 - imported but unused
+import limits
+import more_itertools
+import pandas as pd
+import requests
+import xarray as xr
+from shapely.geometry import MultiPolygon
+from shapely.geometry import Polygon
+
+from .multi import multiprocess
+from .multi import multithread
+from .rate_limit import RateLimit
+from .rate_limit import wait
+from .utils import get_region
+
+
+logger = logging.getLogger(__name__)
+
+# constants
+IOC_RATE_LIMIT = limits.parse("5/second")
+IOC_MAX_DAYS_PER_REQUEST = 30
+IOC_BASE_URL = "http://www.ioc-sealevelmonitoring.org/bgraph.php?code={ioc_code}&output=tab&period={period}&endtime={endtime}"
+IOC_STATIONS_KWARGS = [
+ {"output": "general", "skip_table_rows": 4},
+ {"output": "contacts", "skip_table_rows": 4},
+ {"output": "performance", "skip_table_rows": 8},
+]
+IOC_STATIONS_COLUMN_NAMES = {
+ "general": [
+ "ioc_code",
+ "gloss_id",
+ "country",
+ "location",
+ "connection",
+ "dcp_id",
+ "last_observation_level",
+ "last_observation_time",
+ "delay",
+ "interval",
+ "view",
+ ],
+ "contacts": [
+ "ioc_code",
+ "gloss_id",
+ "lat",
+ "lon",
+ "country",
+ "location",
+ "connection",
+ "contacts",
+ "view",
+ ],
+ "performance": [
+ "ioc_code",
+ "gloss_id",
+ "country",
+ "location",
+ "connection",
+ "added_to_system",
+ "observations_arrived_per_week",
+ "observations_expected_per_week",
+ "observations_ratio_per_week",
+ "observations_arrived_per_month",
+ "observations_expected_per_month",
+ "observations_ratio_per_month",
+ "observations_ratio_per_day",
+ "sample_interval",
+ "average_delay_per_day",
+ "transmit_interval",
+ "view",
+ ],
+}
+IOC_STATION_DATA_COLUMNS_TO_DROP = [
+ "bat",
+ "sw1",
+ "sw2",
+ "(m)",
+]
+IOC_STATION_DATA_COLUMNS = {
+ "Time (UTC)": "time",
+ "aqu(m)": "aqu",
+ "atm(m)": "atm",
+ "bat(V)": "bat",
+ "bub(m)": "bub",
+ "bwl(m)": "bwl",
+ "ecs(m)": "ecs",
+ "enb(m)": "enb",
+ "enc(m)": "enc",
+ "flt(m)": "flt",
+ "pr1(m)": "pr1",
+ "pr2(m)": "pr2",
+ "prs(m)": "prs",
+ "pwl(m)": "pwl",
+ "ra2(m)": "ra2",
+ "rad(m)": "rad",
+ "stp(m)": "stp",
+ "sw1(min)": "sw1",
+ "sw2(min)": "sw2",
+ "wls(m)": "wls",
+}
+
+
+def get_ioc_stations_by_output(output: str, skip_table_rows: int) -> pd.DataFrame:
+ url = f"https://www.ioc-sealevelmonitoring.org/list.php?showall=all&output={output}#"
+ logger.debug("Downloading: %s", url)
+ response = requests.get(url)
+ assert response.ok, f"failed to download: {url}"
+ logger.debug("Downloaded: %s", url)
+ soup = bs4.BeautifulSoup(response.content, "html5lib")
+ logger.debug("Created soup: %s", url)
+ table = soup.find("table", {"class": "nice"})
+ trs = table.find_all("tr")
+ table_contents = "\n".join(str(tr) for tr in trs[skip_table_rows:])
+ html = f"
{table_contents}
"
+ logger.debug("Created table: %s", url)
+ df = pd.read_html(html)[0]
+ logger.debug("Parsed table: %s", url)
+ df.columns = IOC_STATIONS_COLUMN_NAMES[output]
+ df = df.drop(columns="view")
+ return df
+
+
+def normalize_ioc_stations(df: pd.DataFrame) -> gpd.GeoDataFrame:
+ df = df.assign(
+ # fmt: off
+ gloss_id=df.gloss_id.astype(pd.Int64Dtype()),
+ country=df.country.astype("category"),
+ observations_ratio_per_day=df.observations_ratio_per_day.replace("-", "0%").str[:-1].astype(int),
+ observations_ratio_per_week=df.observations_ratio_per_week.replace("-", "0%").str[:-1].astype(int),
+ observations_ratio_per_month=df.observations_ratio_per_month.replace("-", "0%").str[:-1].astype(int),
+ # fmt: on
+ )
+ gdf = gpd.GeoDataFrame(
+ data=df,
+ geometry=gpd.points_from_xy(df.lon, df.lat, crs="EPSG:4326"),
+ )
+ return gdf
+
+
+@functools.cache
+def _get_ioc_stations() -> gpd.GeoDataFrame:
+ """
+ Return IOC station metadata from: http://www.ioc-sealevelmonitoring.org/list.php?showall=all
+
+ :return: ``pandas.DataFrame`` with the station metadata
+ """
+
+ # The IOC web page with the station metadata contains really ugly HTTP code.
+ # creating the `bs4.Beautiful` instance takes as much time as you need to download the page,
+ # therefore multiprocessing is actually faster than multithreading
+ ioc_stations_results = multiprocess(
+ func=get_ioc_stations_by_output,
+ func_kwargs=IOC_STATIONS_KWARGS,
+ )
+ ioc_stations = functools.reduce(pd.merge, (r.result for r in ioc_stations_results))
+ ioc_stations = normalize_ioc_stations(ioc_stations)
+ return ioc_stations
+
+
+def get_ioc_stations(
+ region: Optional[Union[Polygon, MultiPolygon]] = None,
+ lon_min: Optional[float] = None,
+ lon_max: Optional[float] = None,
+ lat_min: Optional[float] = None,
+ lat_max: Optional[float] = None,
+) -> gpd.GeoDataFrame:
+ """
+ Return IOC station metadata from: http://www.ioc-sealevelmonitoring.org/list.php?showall=all
+
+ If `region` is defined then the stations that are outside of the region are
+ filtered out.. If the coordinates of the Bounding Box are defined then
+ stations outside of the BBox are filtered out. If both ``region`` and the
+ Bounding Box are defined, then an exception is raised.
+
+ Note: The longitudes of the IOC stations are in the [-180, 180] range.
+
+ :param region: ``Polygon`` or ``MultiPolygon`` denoting region of interest
+ :param lon_min: The minimum Longitude of the Bounding Box.
+ :param lon_max: The maximum Longitude of the Bounding Box.
+ :param lat_min: The minimum Latitude of the Bounding Box.
+ :param lat_max: The maximum Latitude of the Bounding Box.
+ :return: ``pandas.DataFrame`` with the station metadata
+ """
+ region = get_region(
+ region=region,
+ lon_min=lon_min,
+ lon_max=lon_max,
+ lat_min=lat_min,
+ lat_max=lat_max,
+ symmetric=True,
+ )
+
+ ioc_stations = _get_ioc_stations()
+ if region:
+ ioc_stations = ioc_stations[ioc_stations.within(region)]
+ return ioc_stations
+
+
+def normalize_ioc_station_data(ioc_code: str, df: pd.DataFrame) -> pd.DataFrame:
+ # Each station may have more than one sensors.
+ # Some of the sensors have nothing to do with sea level height. We drop these sensors
+ df = df.rename(columns=IOC_STATION_DATA_COLUMNS)
+ logger.debug("%s: df contains the following columns: %s", ioc_code, df.columns)
+ df = df.drop(columns=IOC_STATION_DATA_COLUMNS_TO_DROP, errors="ignore")
+ if len(df.columns) == 1:
+ # all the data columns have been dropped!
+ msg = f"{ioc_code}: The table does not contain any sensor data!"
+ logger.info(msg)
+ raise ValueError(msg)
+ df = df.assign(
+ ioc_code=ioc_code,
+ # Normalize timestamps to minutes: https://stackoverflow.com/a/14836359/592289
+ # WARNING: the astype() truncates seconds. This can potentially lead to duplicates!
+ time=pd.to_datetime(df.time).astype("datetime64[m]"),
+ )
+ if len(df) != len(df.time.drop_duplicates()):
+ msg = f"{ioc_code}: The sampling ratio is less than 1 minute. Data have been lost"
+ raise ValueError(msg)
+ return df
+
+
+def get_ioc_station_data(
+ ioc_code: str,
+ endtime: Union[str, datetime.date] = datetime.date.today(),
+ period: float = IOC_MAX_DAYS_PER_REQUEST,
+ rate_limit: Optional[RateLimit] = None,
+) -> pd.DataFrame:
+ """Retrieve the TimeSeries of a single IOC station."""
+
+ if rate_limit:
+ while rate_limit.reached(identifier="IOC"):
+ wait()
+
+ endtime = pd.to_datetime(endtime).date().isoformat()
+ url = IOC_BASE_URL.format(ioc_code=ioc_code, endtime=endtime, period=period)
+ logger.debug("Retrieving data from: %s", url)
+ try:
+ df = pd.read_html(url, header=0)[0]
+ except ValueError as exc:
+ if str(exc) == "No tables found":
+ logger.info("No data for %s", ioc_code)
+ else:
+ logger.exception("Something went wrong")
+ raise
+ df = normalize_ioc_station_data(ioc_code=ioc_code, df=df)
+ return df
+
+
+def get_ioc_data(
+ ioc_metadata: pd.DataFrame,
+ endtime: Union[str, datetime.date] = datetime.date.today(),
+ period: float = 1, # one day
+ rate_limit: RateLimit = RateLimit(),
+ disable_progress_bar: bool = False,
+) -> xr.Dataset:
+ """
+ Return the data of the stations specified in ``ioc_metadata`` as an ``xr.Dataset``.
+
+ :param ioc_metadata: A ``pd.DataFrame`` returned by ``get_ioc_stations``.
+ :param endtime: The date of the "end" of the data.
+ :param period: The number of days to be requested. IOC does not support values greater than 30
+ :param rate_limit: The default rate limit is 5 requests/second.
+ :param disable_progress_bar: If ``True`` then the progress bar is not displayed.
+
+ """
+ if period > IOC_MAX_DAYS_PER_REQUEST:
+ msg = (
+ f"Unsupported period. Please choose a period smaller than {IOC_MAX_DAYS_PER_REQUEST}: {period}"
+ )
+ raise ValueError(msg)
+
+ func_kwargs = []
+ for ioc_code in ioc_metadata.ioc_code:
+ func_kwargs.append(
+ dict(period=period, endtime=endtime, ioc_code=ioc_code, rate_limit=rate_limit),
+ )
+
+ results = multithread(
+ func=get_ioc_station_data,
+ func_kwargs=func_kwargs,
+ n_workers=5,
+ print_exceptions=False,
+ disable_progress_bar=disable_progress_bar,
+ )
+
+ datasets = []
+ for result in results:
+ if result.result is not None:
+ df = result.result
+ meta = ioc_metadata[ioc_metadata.ioc_code == result.kwargs["ioc_code"]] # type: ignore[index]
+ ds = df.set_index(["ioc_code", "time"]).to_xarray()
+ ds["lon"] = ("ioc_code", meta.lon)
+ ds["lat"] = ("ioc_code", meta.lat)
+ ds["country"] = ("ioc_code", meta.country)
+ ds["location"] = ("ioc_code", meta.location)
+ datasets.append(ds)
+
+ # in order to keep memory consumption low, let's group the datasets
+ # and merge them in batches
+ datasets = [xr.merge(g for g in group if g) for group in more_itertools.grouper(datasets, 5)]
+ if len(datasets) > 5:
+ # There are quite a few stations left, let's do another grouping
+ datasets = [xr.merge(g for g in group if g) for group in more_itertools.grouper(datasets, 5)]
+ if len(datasets) > 5:
+ # There are quite a few stations left, let's do another grouping
+ datasets = [xr.merge(g for g in group if g) for group in more_itertools.grouper(datasets, 5)]
+ # Do the final merging
+ ds = xr.merge(datasets)
+ return ds
diff --git a/searvey/models.py b/searvey/models.py
index d660210..d293eac 100644
--- a/searvey/models.py
+++ b/searvey/models.py
@@ -5,8 +5,7 @@
import pydantic
-logging.basicConfig(level=10)
-logger = logging.getLogger()
+logger = logging.getLogger(__name__)
class ERDDAPProtocol(str, enum.Enum):
@@ -14,25 +13,40 @@ class ERDDAPProtocol(str, enum.Enum):
GRIDDAP: Final = "griddap"
-class Constraints(pydantic.BaseModel):
+class BBox(pydantic.BaseModel):
lon_min: float
lon_max: float
lat_min: float = pydantic.Field(-90, ge=-90, le=90)
lat_max: float = pydantic.Field(90, ge=-90, le=90)
- start_date: datetime.datetime = pydantic.Field(...)
- end_date: datetime.datetime = pydantic.Field(datetime.datetime.now())
+ class Config:
+ extra = "forbid"
-class SymmetricConstraints(Constraints):
+
+class SymmetricBBox(BBox):
lon_min: float = pydantic.Field(-180, ge=-180, le=180)
lon_max: float = pydantic.Field(180, ge=-180, le=180)
-class AsymmetricConstraints(Constraints):
+class AsymmetricBBox(BBox):
lon_min: float = pydantic.Field(0, ge=0, le=360)
lon_max: float = pydantic.Field(360, ge=0, le=360)
+class Constraints(pydantic.BaseModel):
+ bbox: BBox = pydantic.Field(...)
+ start_date: datetime.datetime = pydantic.Field(...)
+ end_date: datetime.datetime = pydantic.Field(datetime.datetime.now())
+
+
+class SymmetricConstraints(Constraints):
+ bbox: SymmetricBBox = SymmetricBBox()
+
+
+class AsymmetricConstraints(Constraints):
+ bbox: AsymmetricBBox = AsymmetricBBox()
+
+
class ERDDAPDataset(pydantic.BaseModel):
server_url: pydantic.HttpUrl
protocol: ERDDAPProtocol = pydantic.Field(ERDDAPProtocol.TABLEDAP)
diff --git a/searvey/multi.py b/searvey/multi.py
new file mode 100644
index 0000000..09ddfdf
--- /dev/null
+++ b/searvey/multi.py
@@ -0,0 +1,125 @@
+"""
+Helpers for multi processing/threading
+"""
+from __future__ import annotations
+
+import logging
+import os
+from collections.abc import Callable
+from concurrent.futures import as_completed
+from concurrent.futures import ProcessPoolExecutor
+from concurrent.futures import ThreadPoolExecutor
+from typing import Any
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Type
+from typing import Union
+
+import pydantic
+import tqdm
+
+# FTR, `loky.ProcessPoolExecutor` is more robust WRT pickling big objects
+# But since we are not using MultiProcessing here, we don't need to introduce
+# an additional dependency
+# from loky import ProcessPoolExecutor
+
+logger = logging.getLogger(__name__)
+
+
+# https://docs.python.org/3/library/os.html#os.cpu_count
+MAX_AVAILABLE_PROCESSES = len(os.sched_getaffinity(0))
+
+
+class FutureResult(pydantic.BaseModel):
+ exception: Optional[Exception] = None
+ kwargs: Optional[Dict[str, Any]] = None
+ result: Any = None
+
+ class Config:
+ arbitrary_types_allowed: bool = True
+
+ def __hash__(self) -> int:
+ return hash((type(self),) + tuple(self.__dict__.values()))
+
+
+def multi(
+ executor: Union[Type[ProcessPoolExecutor], Type[ThreadPoolExecutor]],
+ func: Callable[..., Any],
+ func_kwargs: List[Dict[str, Any]],
+ n_workers: int,
+ print_exceptions: bool = True,
+ include_kwargs: bool = True,
+ initializer: Optional[Callable[..., Any]] = None,
+ disable_progress_bar: bool = True,
+) -> List[FutureResult]:
+ with tqdm.tqdm(total=len(func_kwargs), disable=disable_progress_bar) as progress_bar:
+ with executor(max_workers=n_workers, initializer=initializer) as xctr:
+ futures_to_kwargs = {xctr.submit(func, **kwargs): kwargs for kwargs in func_kwargs}
+ results = []
+ for future in as_completed(futures_to_kwargs):
+ result_kwargs: Optional[Dict[str, Any]] = futures_to_kwargs[future]
+ try:
+ func_result = future.result()
+ except Exception as exc:
+ if print_exceptions:
+ print(f"<{result_kwargs}> generated an exception: {exc}")
+ if not include_kwargs:
+ result_kwargs = None
+ results.append(FutureResult(exception=exc, kwargs=result_kwargs))
+ else:
+ if not include_kwargs:
+ result_kwargs = None
+ results.append(FutureResult(result=func_result, kwargs=result_kwargs))
+ finally:
+ progress_bar.update(1)
+ return results
+
+
+def multithread(
+ func: Callable[..., Any],
+ func_kwargs: List[Dict[str, Any]],
+ n_workers: int = max(1, MAX_AVAILABLE_PROCESSES - 1),
+ print_exceptions: bool = True,
+ include_kwargs: bool = True,
+ executor: Type[ThreadPoolExecutor] = ThreadPoolExecutor,
+ initializer: Optional[Callable[..., Any]] = None,
+ disable_progress_bar: bool = True,
+) -> List[FutureResult]:
+ results = multi(
+ executor=executor,
+ func=func,
+ func_kwargs=func_kwargs,
+ n_workers=n_workers,
+ print_exceptions=print_exceptions,
+ include_kwargs=include_kwargs,
+ initializer=initializer,
+ disable_progress_bar=disable_progress_bar,
+ )
+ return results
+
+
+def multiprocess(
+ func: Callable[..., Any],
+ func_kwargs: List[Dict[str, Any]],
+ n_workers: int = max(1, MAX_AVAILABLE_PROCESSES - 1),
+ print_exceptions: bool = True,
+ include_kwargs: bool = True,
+ executor: Type[ProcessPoolExecutor] = ProcessPoolExecutor,
+ initializer: Optional[Callable[..., Any]] = None,
+ disable_progress_bar: bool = True,
+) -> List[FutureResult]:
+ if n_workers > MAX_AVAILABLE_PROCESSES:
+ msg = f"The maximum available processes are {MAX_AVAILABLE_PROCESSES}, not: {n_workers}"
+ raise ValueError(msg)
+ results = multi(
+ executor=executor,
+ func=func,
+ func_kwargs=func_kwargs,
+ n_workers=n_workers,
+ print_exceptions=print_exceptions,
+ include_kwargs=include_kwargs,
+ initializer=initializer,
+ disable_progress_bar=disable_progress_bar,
+ )
+ return results
diff --git a/searvey/rate_limit.py b/searvey/rate_limit.py
new file mode 100644
index 0000000..e007ef6
--- /dev/null
+++ b/searvey/rate_limit.py
@@ -0,0 +1,33 @@
+import random
+import time
+from typing import Type
+
+import limits
+
+
+def wait(wait_time: float = 0.1, jitter: bool = True) -> None:
+ """Wait for `wait_time` + some random `jitter`"""
+ if jitter:
+ # Set jitter to be <= 1% of wait_time
+ jitter_time = random.random() / (1 / wait_time * 100)
+ else:
+ jitter_time = 0
+ time.sleep(wait_time + jitter_time)
+
+
+class RateLimit:
+ def __init__(
+ self,
+ rate_limit: limits.RateLimitItem = limits.parse("5/second"),
+ storage: Type[limits.storage.Storage] = limits.storage.MemoryStorage,
+ strategy: Type[limits.strategies.RateLimiter] = limits.strategies.MovingWindowRateLimiter,
+ ) -> None:
+ self.rate_limit = rate_limit
+ self.storage = storage()
+ self.strategy = strategy(self.storage)
+
+ def reached(self, identifier: str, cost: int = 1) -> bool:
+ return not self.strategy.hit(
+ self.rate_limit,
+ identifier,
+ )
diff --git a/searvey/station.py b/searvey/station.py
deleted file mode 100644
index 1d0b774..0000000
--- a/searvey/station.py
+++ /dev/null
@@ -1,109 +0,0 @@
-from abc import ABC
-from abc import abstractmethod
-from datetime import datetime
-from enum import Enum
-from typing import Any
-from typing import Dict
-
-from pandas import DataFrame
-from shapely.geometry import Point
-from xarray import Dataset
-
-
-class StationDataProduct(Enum):
- pass
-
-
-class StationDataInterval(Enum):
- pass
-
-
-class StationDatum(Enum):
- pass
-
-
-class StationStatus(Enum):
- ACTIVE = "active"
- DISCONTINUED = "discontinued"
-
-
-class Station(ABC):
- """
- abstraction of a specific data station
- """
-
- id: str
- location: Point
-
- def __init__(self, id: str, location: Point): # pylint: disable=redefined-builtin
- self.id = id
- self.location = location
-
- @abstractmethod
- def product(
- self,
- product: StationDataProduct,
- start_date: datetime,
- end_date: datetime = None,
- interval: StationDataInterval = None,
- datum: StationDatum = None,
- ) -> Dataset:
- """
- retrieve data for the current station within the specified parameters
-
- :param product: name of data product
- :param start_date: start date
- :param end_date: end date
- :param interval: time interval of data
- :param datum: vertical datum
- :return: data for the current station within the specified parameters
- """
- raise NotImplementedError()
-
- def __str__(self) -> str:
- return f'{self.__class__.__name__} - "{self.id}" {self.location}'
-
-
-class StationQuery(ABC):
- """
- abstraction of an individual station data query
- """
-
- station_id: str
- product: StationDataProduct
- start_date: datetime
- end_date: datetime
- interval: StationDataInterval
- datum: StationDatum
-
- def __init__(
- self,
- station_id: str,
- product: StationDataProduct,
- start_date: datetime,
- end_date: datetime = None,
- interval: StationDataInterval = None,
- datum: StationDatum = None,
- ):
- self.station_id = station_id
- self.product = product
- self.start_date = start_date
- self.end_date = end_date
- self.interval = interval
- self.datum = datum
-
- @property
- @abstractmethod
- def query(self) -> Dict[str, Any]:
- raise NotImplementedError()
-
- @property
- @abstractmethod
- def data(self) -> DataFrame:
- """
- :return: data for the current query parameters
- """
- raise NotImplementedError()
-
- def __str__(self) -> str:
- return f'{self.__class__.__name__} - {self.product} at station "{self.station_id}" between {self.start_date} and {self.end_date} over {self.interval} in {self.datum}'
diff --git a/searvey/uhslc.py b/searvey/uhslc.py
index 8769c10..6033f38 100644
--- a/searvey/uhslc.py
+++ b/searvey/uhslc.py
@@ -9,6 +9,7 @@
import pydantic
from searvey.erddap import query_erddap
+from searvey.models import AsymmetricBBox
from searvey.models import AsymmetricConstraints
from searvey.models import ERDDAPDataset
from searvey.utils import lon3_to_lon1
@@ -77,14 +78,17 @@ def get_uhslc_data(
lon_max: float = 360,
timeout: int = 10,
) -> pd.DataFrame:
- constraints = AsymmetricConstraints(
- start_date=start_date,
- end_date=end_date or datetime.datetime.now(),
+ bbox = AsymmetricBBox(
lat_min=lat_min,
lat_max=lat_max,
lon_min=lon_min,
lon_max=lon_max,
)
+ constraints = AsymmetricConstraints(
+ bbox=bbox,
+ start_date=start_date,
+ end_date=end_date or datetime.datetime.now(),
+ )
df = query_erddap(dataset=SOEST_UHSLC, constraints=constraints, timeout=timeout)
df = normalize_names(df)
df = normalize_longitudes(df)
diff --git a/searvey/utils.py b/searvey/utils.py
index 1631493..ba3e610 100644
--- a/searvey/utils.py
+++ b/searvey/utils.py
@@ -1,3 +1,10 @@
+from typing import Union
+
+from shapely.geometry import box
+from shapely.geometry import MultiPolygon
+from shapely.geometry import Polygon
+
+from . import models
from .custom_types import ScalarOrArray
@@ -12,3 +19,74 @@ def lon1_to_lon3(lon1: ScalarOrArray) -> ScalarOrArray:
def lon3_to_lon1(lon3: ScalarOrArray) -> ScalarOrArray:
return ((lon3 + 180) % 360) - 180
+
+
+def get_region_from_bbox_corners(
+ lon_min: Union[float, None] = None,
+ lon_max: Union[float, None] = None,
+ lat_min: Union[float, None] = None,
+ lat_max: Union[float, None] = None,
+ symmetric: bool = True,
+) -> Polygon:
+ """Return a ``shapely.geometry.Polygon`` from the corners of a Bounding Box."""
+ bbox_kwargs = {
+ "lon_min": lon_min,
+ "lon_max": lon_max,
+ "lat_min": lat_min,
+ "lat_max": lat_max,
+ }
+ if symmetric:
+ klass = models.SymmetricBBox
+ else:
+ klass = models.AsymmetricBBox
+
+ # Create the BBox (i.e. validate input values)
+ # Not the most beautiful code in the world, but Pydantic needs keyword arguments
+ # and we want to only pass arguments that have actual values and not None
+ # moreover, we create a new object because mypy was complaining about redefining
+ filtered_bbox_kwargs: dict[str, float] = {k: v for (k, v) in bbox_kwargs.items() if v}
+ bbox = klass(**filtered_bbox_kwargs)
+
+ # Create the region
+ region = box(
+ minx=bbox.lon_min,
+ miny=bbox.lat_min,
+ maxx=bbox.lon_max,
+ maxy=bbox.lat_max,
+ )
+ return region
+
+
+def get_region(
+ region: Union[Union[Polygon, MultiPolygon], None] = None,
+ lon_min: Union[float, None] = None,
+ lon_max: Union[float, None] = None,
+ lat_min: Union[float, None] = None,
+ lat_max: Union[float, None] = None,
+ symmetric: bool = True,
+) -> Union[Polygon, None]:
+ """
+ Return a shapely Region
+
+ The region can either be a ``shapely.geometry`` object or it can be defined
+ from the corners of a Bounding Box.
+
+ If both a ``region`` and the corners are specified then an Exception is raised.
+
+ - If ``symmetric`` is ``False``, then it is assumed that Longitude ∈ [0, 360).
+ - If ``symmetric`` is ``True``, then it is assumed that Longitude ∈ [-180, 180).
+ """
+ if any((lon_min, lon_max, lat_min, lat_max)):
+ # Ensure that only one of region and bbox have been specified, not both
+ if region:
+ msg = "You must specify either `region` or the `BBox` corners, not both"
+ raise ValueError(msg)
+
+ region = get_region_from_bbox_corners(
+ lon_min=lon_min,
+ lon_max=lon_max,
+ lat_min=lat_min,
+ lat_max=lat_max,
+ symmetric=symmetric,
+ )
+ return region
diff --git a/tests/cassettes/ioc_test/test_get_ioc_stations.yaml b/tests/cassettes/ioc_test/test_get_ioc_stations.yaml
new file mode 100644
index 0000000..c17101b
--- /dev/null
+++ b/tests/cassettes/ioc_test/test_get_ioc_stations.yaml
@@ -0,0 +1,25441 @@
+interactions:
+- request:
+ body: null
+ headers:
+ Accept:
+ - '*/*'
+ Accept-Encoding:
+ - gzip, deflate
+ Connection:
+ - keep-alive
+ User-Agent:
+ - python-requests/2.27.1
+ method: GET
+ uri: https://www.ioc-sealevelmonitoring.org/list.php?showall=all&output=general
+ response:
+ body:
+ string: "\r\n\r\n\r\nSEA LEVEL
+ STATION MONITORING FACILITY\r\n\r\n\r\n\r\n\r\n\r\n\r\n\t