diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..1bd4946 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,37 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' +--- + +### Describe the bug + +A clear and concise description of what the bug is. + +### To Reproduce + +Steps to reproduce the behavior: + +1. Import '...' +2. Call '....' +3. See error + +### Expected behavior + +A clear and concise description of what you expected to happen. + +### Screenshots + +If applicable, add screenshots to help explain your problem. + +### Desktop (please complete the following information) + +- OS: `[e.g. linux]` +- Python version(s): `[e.g. 3.12]` +- Dependencies versions: `[e.g. pydantic 2.6.4]` + +### Additional context + +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yaml b/.github/ISSUE_TEMPLATE/config.yaml new file mode 100644 index 0000000..4d3b612 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yaml @@ -0,0 +1,2 @@ +--- +blank_issues_enabled: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..61af533 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,23 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' +--- + +### Is your feature request related to a problem? Please describe + +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +### Describe the solution you'd like + +A clear and concise description of what you want to happen. + +### Describe alternatives you've considered + +A clear and concise description of any alternative solutions or features you've considered. + +### Additional context + +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..b932a9c --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,18 @@ +### What? + +Explain with one or more sentences what this change is doing. + +### Have you done? + +- [ ] Code tests +- [ ] Update documentation +- [ ] Update [changelog](https://github.com/MAIF/arta/blob/main/CHANGELOG.md) + +### Details to be checked: (optional) + +If needed, add some details here in order to verify the change (e.g., how to test). + +### Linked issues: (optional) + +- Close ... +- Close ... diff --git a/.github/workflows/ci-cd-mkdocs.yml b/.github/workflows/ci-cd-mkdocs.yml new file mode 100644 index 0000000..372c445 --- /dev/null +++ b/.github/workflows/ci-cd-mkdocs.yml @@ -0,0 +1,52 @@ +--- + name: Documentation CI/CD + on: + push: + branches: + - main + tags: + - '*' # Later: \b[0-9]\.[0-9]+\.[0-9]+[ab]?[0-9]?\b + pull_request: + types: + - opened + - synchronize + branches: + - main + + jobs: + build: + if: ${{ github.actor != 'dependabot[bot]' }} + name: Build doc + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Install package with optional dependency 'doc' + run: | + python -m pip install --upgrade pip + pip install .[doc] + - name: Run MkDocs build + working-directory: ./docs + run: mkdocs build + - name: Upload Pages artifact + uses: actions/upload-pages-artifact@v3 + with: + path: "docs/site/" + publish: + if: success() && startsWith(github.ref, 'refs/tags') + name: Publish doc + needs: build + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e1d0d98 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# Archives and compressed files +*.7z +*.7Z +*.gz +*.GZ +*.rar +*.RAR +*.tar +*.TAR +*.tar.* +*.TAR.* +*.tgz +*.TGZ +*.zip +*.ZIP + +# Packages +*.egg +*.egg-info/ +*.jar +*.war +*.ear + +# Executable binary files +*.exe +*.EXE +*.msi +*.MSI +*.dll +*.DLL + +# Log files +*.log +*.LOG +*.log.* +*.LOG.* +logs/* +LOGS/* + +# Build directories +build/ +target/* +*/target/* +cache/ + +# Crash files +hs_err_pid* + +# Jupyter Notebook checkpoints +.ipynb_checkpoints + +# Configuration files +.htaccess + +# macOS specific files +*.DS_Store + +# Data files +*.csv +data/ + +# Compiled Python files +*.pyc + +# Environments +.env + +# Development tools +.vscode +.coverage + +# mkdocs documentation +*/site \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..023c8bf --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,22 @@ +default_language_version: + python: python3 +repos: +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.0 + hooks: + - id: ruff + args: [ --fix ] + - id: ruff-format +- repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.13.0 + hooks: + - id: mypy + args: [--config-file=pyproject.toml] + files: src + additional_dependencies: [types-pytz,types-requests,types-python-dateutil] +- repo: https://github.com/compilerla/conventional-pre-commit + rev: v3.6.0 + hooks: + - id: conventional-pre-commit + stages: [commit-msg] + args: [] \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5d60a79 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +## TODO diff --git a/LICENSE.md b/LICENSE.md deleted file mode 100644 index d9a10c0..0000000 --- a/LICENSE.md +++ /dev/null @@ -1,176 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..fb31878 --- /dev/null +++ b/README.md @@ -0,0 +1,67 @@ +# Meteole + +**Meteole** provides utilities to facilitate data retrieval from the Météo-France APIs using Python. It specifically supports the AROME and ARPEGE forecast models and vigilance bulletins. + +## 🛠 Installation + +```python +pip install meteole +``` + +## 🕐 Quickstart + +### Obtain an API token or key + +Create an account on [the Météo-France API portal](https://portail-api.meteofrance.fr/). Next, subscribe to the desired APIs (Arome, Arpege, etc.). Retrieve the API token (or key) by going to “Mes APIs” and then “Générer token”. + +### 🌧️ AROME, ARPEGE + +The flagship weather forecasting models of Météo-France are accessible via the Météo-France APIs. + +| Characteristics | AROME | ARPEGE | +|------------------|----------------------|----------------------| +| Resolution | 1.3 km | 10 km | +| Update Frequency | Every 3 hours | Every 6 hours | +| Forecast Range | Up to 51 hours | Up to 114 hours | + +```python +from meteole import arome + +arome_client = arome.AromeForecast(application_id=APPLICATION_ID) # APPLICATION_ID found on portail.meteo-france.Fr + +# let's look at the latest wind gusts +indicator = 'V_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND' + +# or check any other indicator in the list +print(arome_client.indicators) + +# get the latest MeteoFrance forecasts concerning this indicator +# all default parameters are printed to make sure you are in control +df_arome = arome_client.get_coverage(indicator) + +# default height doesn't suit you? change it easily +df_arome = arome_client.get_coverage(indicator, height=10) +``` + +### ⚠️ VIGILANCE METEO FRANCE + +Meteo France offers a vigilance bulletin that provides nationwide predictions of potential weather risks. + +For data usage, access the predicted phenomena to trigger modeling based on the forecasts. + +```python +from meteole import Vigilance + +client = Vigilance(application_id=APPLICATION_ID) + +df_phenomenon, df_timelaps = client.get_phenomenon() # pour accéder aux phénomènes prévus + +textes_vigilance = client.get_textes_vigilance() # pour accéder aux bulletins de vigilance + +client.get_vignette() # pour afficher les vignettes +``` + +vignette de vigilance + +To have more documentation from MeteoFrance in Vigilance Bulletin : +- [Meteo France Documentation](https://donneespubliques.meteofrance.fr/?fond=produit&id_produit=305&id_rubrique=50) diff --git a/assets/token_api.png b/assets/token_api.png new file mode 100644 index 0000000..a06ef5b Binary files /dev/null and b/assets/token_api.png differ diff --git a/assets/vignette_exemple.png b/assets/vignette_exemple.png new file mode 100644 index 0000000..18cfd56 Binary files /dev/null and b/assets/vignette_exemple.png differ diff --git a/docs/mkdocs.yaml b/docs/mkdocs.yaml new file mode 100644 index 0000000..62c7a9d --- /dev/null +++ b/docs/mkdocs.yaml @@ -0,0 +1,84 @@ +site_name: Meteole +site_url: https://maif.github.io/meteole +repo_url: https://github.com/MAIF/meteole +repo_name: MAIF/meteole +site_author: OSSbyMAIF Team +docs_dir: pages +theme: + name: 'material' + logo: assets/img/svg/meteole-fond-clair.svg + favicon: assets/img/svg/meteole-git.svg + palette: + # Palette toggle for automatic mode + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + primary: white + accent: red + + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: white + accent: red + toggle: + icon: material/brightness-7 + name: Switch to dark mode + + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: red + toggle: + icon: material/brightness-4 + name: Switch to system preference + font: + text: 'Roboto' + code: 'Roboto Mono' + language: en + features: + - content.tabs.link + - content.code.annotate + - content.code.copy + - content.code.select + - announce.dismiss + - navigation.tabs + - search.highlight + - search.share +markdown_extensions: + tables: + admonition: + pymdownx.details: +# pymdownx.extra: + pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + pymdownx.tabbed: + alternate_style: true + pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + use_pygments: true + linenums: true + pymdownx.inlinehilite: + pymdownx.snippets: + pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format '' +plugins: + - mkdocstrings + - search +nav: + - Home: + - Welcome: home.md + - Why use Meteole?: why.md + - Installation: installation.md + - User Guide: + - How to: how_to.md +extra_css: + - assets/css/mkdocs_extra.css \ No newline at end of file diff --git a/docs/pages/assets/css/mkdocs_extra.css b/docs/pages/assets/css/mkdocs_extra.css new file mode 100644 index 0000000..c17ef77 --- /dev/null +++ b/docs/pages/assets/css/mkdocs_extra.css @@ -0,0 +1,17 @@ +:root { + --md-admonition-icon--beta: url('data:image/svg+xml;charset=utf-8,') +} +.md-typeset .admonition.beta, +.md-typeset details.beta { + border-color: #CF363B; +} +.md-typeset .beta > .admonition-title, +.md-typeset .beta > summary { + background-color: #CF363B; +} +.md-typeset .beta > .admonition-title::before, +.md-typeset .beta > summary::before { + background-color: white; + -webkit-mask-image: var(--md-admonition-icon--beta); + mask-image: var(--md-admonition-icon--beta); +} \ No newline at end of file diff --git a/docs/pages/assets/css/style.css b/docs/pages/assets/css/style.css new file mode 100644 index 0000000..9065cd9 --- /dev/null +++ b/docs/pages/assets/css/style.css @@ -0,0 +1,314 @@ +body { + font-family: 'Arvo', serif; + background-color: #2E3031; +} + +.otoColor { + background-color: #2596be; +} + +a { + color: #2596be; + text-decoration: underline; +} + +a:hover { + color: #FFF; +} + +.home { + position: fixed; + z-index: 1001; + margin-left: 10px; + margin-top: 10px; +} + +.home a { + color: #2E3031; +} + +.home a:hover { + color: #af8900; +} + +.tagN2 { + width: 20%; + display: block; + margin: 20px auto; +} + +.contentN2 { + padding-top: 30px; +} + +.contentN2 .col-md-6 { + padding-top: 70px; +} + +.colDashRight { + border-right: 1px dashed #F2F2F2; + padding-right: 60px; +} + +.titleOnLine { + color: #FFF; + font-size: 36px; + text-transform: uppercase; + padding-left: 0px; +} + +.titleOnLine li { + list-style-type: none; +} + +.titleOnLine li:nth-child(even) { + color: #2596be; +} + +h1 { + font-size: 24px; +} + +h2 { + color: #FFF; + font-size: 21px; + text-transform: uppercase; +} + +.manifesto { + border: 0; + position: fixed; + right: 0; + top: 0; + z-index: 9000; +} + +.intro { + font-family: 'Roboto', serif; + font-size: 18px; + color: #FFF; +} + +.details { + font-family: 'Roboto', serif; + font-size: 14px; + font-weight: 300; + color: #fff; + margin-top: 40px; +} + +.details ul { + padding-left: 20px +} + +.more-details { + text-align: center; + color: #fff; + padding-left: 60px; +} + +.more-details .pictoPortrait { + height: 50px; +} + +.youtube_player{ + margin-bottom: 20px; +} + +.youtube_player { + position:relative; + padding-bottom:56.25%; + padding-top:30px; + height:0; + overflow:hidden; +} + +.youtube_player iframe, .youtube_player object, .youtube_player embed { + position:absolute; + top:0; + left:0; + width:100%; + height:100%; +} + +.youtube_player .tac_activate { + background: #2E3031; +} + +.more-details .pictoPaysage { + margin-top: 20px; + width: 70px +} + +.outer-circle { + height: 100px; + width: 100px; + position: relative; + border: 3px solid #2596be; + border-radius: 50%; + margin: auto; + margin-top: 50px; +} + +.inner-circle { + position: absolute; + background: #2596be; + border-radius: 50%; + height: 90px; + width: 90px; + top: 50%; + left: 50%; + margin: -45px 0px 0px -45px; + color: #2E3031; +} + +.inner-circle p { + margin-top: 40%; +} + +.inner-circle--lowerText { + font-size: 13px; +} + +.sidebar { + background: linear-gradient(90deg, #2596be 50%, #2E3031 50%); + position: fixed; + top: 0; + bottom: 0; + left: 0; + z-index: 1000; + display: block; + overflow-x: hidden; + overflow-y: auto; + border-right: none; +} + +.sidebar-container { + display: flex; + height: 100%; + flex-direction: column; + justify-content: space-between; +} + +.sidebar-content { + flex-grow: 1; + padding-top: 20px; +} + +.footer-content { + width: 300%; + margin-left: -100%; + overflow: hidden; + bottom: 0; +} + +.footer-contentN2 { + position: relative; + width: 50%; + z-index: 1000; + margin-left: -5%; +} + +@media screen and (max-width: 991px) { + .intro { + font-size: 16px; + } + .colDashRight { + border: none; + padding-right: 15px; + } + .titleOnLine { + font-size: 26px; + } + .titleOnLine li { + text-align: center; + } + .details li, .details { + list-style-type: none; + text-align: center; + } + .more-details { + padding-left: 15px; + } +} + +@media screen and (max-width: 770px) { + .home { + position: absolute; + } + .home .fa-home { + font-size: 1.5em; + } + .sidebar-content { + width: 200px; + padding-left: 30px; + } + .contentN2 { + padding-left: 30px; + } + .sidebar-container { + flex-direction: column; + } + .sidebar { + background: linear-gradient(180deg, #2596be 50%, #2E3031 50%); + position: inherit; + } + .footer-contentN2 { + margin-left: 0px; + margin-top: 20px; + width: 100%; + background-color: #2596be; + } + .intro { + text-align: left; + } + .titleOnLine { + text-align: left; + } + .titleOnLine li { + display: inline-block; + } + .more-details { + text-align: left; + } + .contentCircle { + margin-right: 20px; + } + .tagN2 { + margin-top: 10px; + } + .titleOnLine li { + text-align: left; + } + .details li, .details { + text-align: left; + } + .details ul { + padding-left: 0px; + } + .outer-circle { + margin-left: 0px; + text-align: center; + } + .linkN2 { + padding-left: 20px; + } +} + +@media screen and (max-width: 400px) { + .outer-circle { + height: 80px; + width: 80px; + } + .inner-circle { + height: 70px; + width: 70px; + margin: -35px 0px 0px -35px; + } + .inner-circle p { + font-size: 12px; + } + .inner-circle p.inner-circle--lowerText { + font-size: 10px; + } +} \ No newline at end of file diff --git a/docs/pages/assets/img/contributing/meteole-clone.png b/docs/pages/assets/img/contributing/meteole-clone.png new file mode 100644 index 0000000..7b2113d Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-clone.png differ diff --git a/docs/pages/assets/img/contributing/meteole-compare-pr.png b/docs/pages/assets/img/contributing/meteole-compare-pr.png new file mode 100644 index 0000000..d1c755e Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-compare-pr.png differ diff --git a/docs/pages/assets/img/contributing/meteole-create-pr.png b/docs/pages/assets/img/contributing/meteole-create-pr.png new file mode 100644 index 0000000..ba1dd67 Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-create-pr.png differ diff --git a/docs/pages/assets/img/contributing/meteole-fork.png b/docs/pages/assets/img/contributing/meteole-fork.png new file mode 100644 index 0000000..8ce4d99 Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-fork.png differ diff --git a/docs/pages/assets/img/contributing/meteole-pr-branch.png b/docs/pages/assets/img/contributing/meteole-pr-branch.png new file mode 100644 index 0000000..547243e Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-pr-branch.png differ diff --git a/docs/pages/assets/img/contributing/meteole-pr-description.png b/docs/pages/assets/img/contributing/meteole-pr-description.png new file mode 100644 index 0000000..a31a969 Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-pr-description.png differ diff --git a/docs/pages/assets/img/contributing/meteole-pr.png b/docs/pages/assets/img/contributing/meteole-pr.png new file mode 100644 index 0000000..ec91f26 Binary files /dev/null and b/docs/pages/assets/img/contributing/meteole-pr.png differ diff --git a/docs/pages/assets/img/png/vignette_exemple.png b/docs/pages/assets/img/png/vignette_exemple.png new file mode 100644 index 0000000..18cfd56 Binary files /dev/null and b/docs/pages/assets/img/png/vignette_exemple.png differ diff --git a/docs/pages/assets/img/svg/configure.svg b/docs/pages/assets/img/svg/configure.svg new file mode 100644 index 0000000..fcd6038 --- /dev/null +++ b/docs/pages/assets/img/svg/configure.svg @@ -0,0 +1 @@ +Fichier 1 \ No newline at end of file diff --git a/docs/pages/assets/img/svg/etiquette.svg b/docs/pages/assets/img/svg/etiquette.svg new file mode 100644 index 0000000..650e48c --- /dev/null +++ b/docs/pages/assets/img/svg/etiquette.svg @@ -0,0 +1 @@ +Python \ No newline at end of file diff --git a/docs/pages/assets/img/svg/footer.svg b/docs/pages/assets/img/svg/footer.svg new file mode 100644 index 0000000..0a9fab1 --- /dev/null +++ b/docs/pages/assets/img/svg/footer.svg @@ -0,0 +1,237 @@ + + + + + + Plan de travail 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/pages/assets/img/svg/latency.svg b/docs/pages/assets/img/svg/latency.svg new file mode 100644 index 0000000..09beccc --- /dev/null +++ b/docs/pages/assets/img/svg/latency.svg @@ -0,0 +1 @@ +Fichier 2 \ No newline at end of file diff --git a/docs/pages/assets/img/svg/meteole-fond-clair.svg b/docs/pages/assets/img/svg/meteole-fond-clair.svg new file mode 100644 index 0000000..fadf6a5 --- /dev/null +++ b/docs/pages/assets/img/svg/meteole-fond-clair.svg @@ -0,0 +1,2 @@ + + diff --git a/docs/pages/assets/js/tarteaucitron/LICENSE b/docs/pages/assets/js/tarteaucitron/LICENSE new file mode 100644 index 0000000..eee43b8 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 AmauriC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/README.md b/docs/pages/assets/js/tarteaucitron/README.md new file mode 100644 index 0000000..236e47a --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/README.md @@ -0,0 +1,101 @@ +[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.me/SASAICAGENCY) + + +tarteaucitron.js +================ +Comply to the european cookie law is simple with the french *tarte au citron*. + +# What is this script? +The european cookie law regulates the management of cookies and you should ask your visitors their consent before exposing them to third party services. + +Clearly this script will: +- Disable all services by default, +- Display a banner on the first page view and a small one on other pages, +- Display a panel to allow or deny each services one by one, +- Activate services on the second page view if not denied, +- Store the consent in a cookie for 365 days. + +Bonus: +- Load service when user click on Allow (without reload of the page), +- Incorporate a fallback system (display a link instead of social button and a static banner instead of advertising). + +## Supported services +* Advertising network + * Amazon + * Clicmanager + * Criteo + * FERank (pub) + * Google Adsense + * Google Adsense Search (form) + * Google Adsense Search (result) + * Google Adwords (conversion) + * Google Adwords (remarketing) + * Pubdirecte + * Twenga + * vShop + +* APIs + * Google jsapi + * Google Maps + * Google Tag Manager + * Timeline JS + * Typekit (adobe) + +* Audience measurement + * Alexa + * Clicky + * Crazyegg + * FERank + * Get+ + * Google Analytics (ga.js) + * Google Analytics (universal) + * StatCounter + * VisualRevenue + * Xiti + +* Comment + * Disqus + * Facebook (commentaire) + +* Social network + * AddThis + * AddToAny (feed) + * AddToAny (share) + * eKomi + * Facebook + * Facebook (like box) + * Google+ + * Google+ (badge) + * Linkedin + * Pinterest + * Shareaholic + * ShareThis + * Twitter + * Twitter (cards) + * Twitter (timelines) + +* Support + * UserVoice + * Zopim + +* Video + * Calameo + * Dailymotion + * Prezi + * SlideShare + * Vimeo + * YouTube + + +## Visitors outside the EU +In PHP for example, you can bypass all the script by setting this var `tarteaucitron.user.bypass = true;` if the visitor is not in the EU. + +## Tested on +- IE 6+ +- FF 3+ +- Safari 4+ +- Chrome 14+ +- Opera 10+ + +# Installation guide +[Visit opt-out.ferank.eu](https://opt-out.ferank.eu/) \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/advertising.js b/docs/pages/assets/js/tarteaucitron/advertising.js new file mode 100644 index 0000000..813e11b --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/advertising.js @@ -0,0 +1 @@ +tarteaucitronNoAdBlocker = true; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/css/tarteaucitron.css b/docs/pages/assets/js/tarteaucitron/css/tarteaucitron.css new file mode 100644 index 0000000..d0f24c8 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/css/tarteaucitron.css @@ -0,0 +1,545 @@ +/*** + * Responsive layout for the control panel + */ +@media screen and (max-width:479px) { + #tarteaucitron .tarteaucitronLine .tarteaucitronName { + width: 90% !important; + } + + #tarteaucitron .tarteaucitronLine .tarteaucitronAsk { + float: left !important; + margin: 10px 15px 5px; + } +} + +@media screen and (max-width:767px) { + #tarteaucitronAlertSmall #tarteaucitronCookiesListContainer, #tarteaucitron { + background: #fff; + border: 0 !important; + bottom: 0 !important; + height: 100% !important; + left: 0 !important; + margin: 0 !important; + max-height: 100% !important; + max-width: 100% !important; + top: 0 !important; + width: 100% !important; + } + + #tarteaucitron .tarteaucitronBorder { + border: 0 !important; + } + + #tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList { + border: 0 !important; + } + + #tarteaucitron #tarteaucitronServices .tarteaucitronTitle { + text-align: left !important; + } +} + +@media screen and (min-width:768px) and (max-width:991px) { + #tarteaucitron { + border: 0 !important; + left: 0 !important; + margin: 0 5% !important; + max-height: 80% !important; + width: 90% !important; + } +} + +/*** + * Common value + */ +#tarteaucitron * { + zoom: 1; +} + +#tarteaucitron .clear { + clear: both; +} + +#tarteaucitron a { + color: rgb(66, 66, 66); + font-size: 11px; + font-weight: 700; + text-decoration: none; +} + +#tarteaucitronAlertBig a, #tarteaucitronAlertSmall a { + color: #fff; +} + +#tarteaucitron b { + font-size: 22px; + font-weight: 500; +} + +/*** + * Root div added just before + */ +#tarteaucitronRoot { + left: 0; + position: absolute; + right: 0; + top: 0; + width: 100%; +} + +#tarteaucitronRoot * { + box-sizing: initial; + color: #333; + font-family: sans-serif !important; + font-size: 14px; + line-height: normal; + vertical-align: initial; +} + +/*** + * Control panel + */ +#tarteaucitronBack { + background: #fff; + display: none; + height: 100%; + left: 0; + opacity: 0.7; + position: fixed; + top: 0; + width: 100%; + z-index: 2147483646; +} + +#tarteaucitron { + display: none; + max-height: 80%; + left: 50%; + margin: 0 auto 0 -430px; + padding: 0; + position: fixed; + top: 6%; + width: 860px; + z-index: 2147483647; +} + +#tarteaucitron .tarteaucitronBorder { + background: #fff; + border: 2px solid #333; + border-top: 0; + height: auto; + overflow: auto; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronClosePanelCookie, +#tarteaucitron #tarteaucitronClosePanel { + background: #333333; + color: #fff; + cursor: pointer; + font-size: 12px; + font-weight: 700; + text-decoration: none; + padding: 4px 0; + position: absolute; + right: 0; + text-align: center; + width: 70px; +} + +#tarteaucitron #tarteaucitronDisclaimer { + color: #555; + font-size: 12px; + margin: 15px auto 0; + width: 80%; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronHidden, +#tarteaucitron #tarteaucitronServices .tarteaucitronHidden { + background: rgba(51, 51, 51, 0.07); +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronHidden { + display: none; + position: relative; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronTitle, +#tarteaucitron #tarteaucitronServices .tarteaucitronTitle, +#tarteaucitron #tarteaucitronInfo, +#tarteaucitron #tarteaucitronServices .tarteaucitronDetails { + background: #333; + color: #fff; + display: inline-block; + font-size: 14px; + font-weight: 700; + margin: 20px 0px 0px; + padding: 5px 20px; + text-align: left; + width: auto; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine .tarteaucitronName a, +#tarteaucitron #tarteaucitronServices .tarteaucitronTitle a { + color: #fff; + font-weight: 500; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine .tarteaucitronName a:hover, +#tarteaucitron #tarteaucitronServices .tarteaucitronTitle a:hover { + text-decoration: none !important; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine .tarteaucitronName a { + font-size: 22px; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronTitle a { + font-size: 14px; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronTitle { + padding: 5px 10px; +} + +#tarteaucitron #tarteaucitronInfo, +#tarteaucitron #tarteaucitronServices .tarteaucitronDetails { + color: #fff; + display: none; + font-size: 12px; + font-weight: 500; + margin-top: 0; + max-width: 270px; + padding: 20px; + position: absolute; + z-index: 2147483647; +} + +#tarteaucitron #tarteaucitronInfo a { + color: #fff; + text-decoration: underline; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine:hover { + background: rgba(51, 51, 51, 0.2); +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine { + background: rgba(51, 51, 51, 0.1); + border-left: 5px solid transparent; + margin: 0; + overflow: hidden; + padding: 15px 5px; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine { + background: #333; + border: 3px solid #333; + border-left: 9px solid #333; + border-top: 5px solid #333; + margin-bottom: 0; + margin-top: 21px; + position: relative; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine:hover { + background: #333; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine .tarteaucitronName { + margin-left: 15px; + margin-top: 2px; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine .tarteaucitronName b { + color: #fff; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronMainLine .tarteaucitronAsk { + margin-top: 0px !important; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine .tarteaucitronName { + display: inline-block; + float: left; + margin-left: 10px; + text-align: left; + width: 50%; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine .tarteaucitronName a:hover { + text-decoration: underline; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine .tarteaucitronAsk { + display: inline-block; + float: right; + margin: 7px 15px 0; + text-align: right; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine .tarteaucitronAsk .tarteaucitronAllow, +#tarteaucitron #tarteaucitronServices .tarteaucitronLine .tarteaucitronAsk .tarteaucitronDeny, +.tac_activate .tarteaucitronAllow { + background: gray; + border-radius: 4px; + color: #fff; + cursor: pointer; + display: inline-block; + padding: 6px 10px; + text-align: center; + text-decoration: none; + width: auto; +} + +#tarteaucitron #tarteaucitronServices .tarteaucitronLine .tarteaucitronName .tarteaucitronListCookies { + color: #333; + font-size: 12px; +} + +/*** + * Big alert + */ +.tarteaucitronAlertBigTop { + top: 0; +} + +.tarteaucitronAlertBigBottom { + bottom: 0; +} + +#tarteaucitronAlertBig { + background: #333; + color: #fff; + display: none; + font-size: 15px !important; + left: 0; + padding: 5px 5%; + position: fixed; + text-align: center; + width: 90%; + box-sizing: content-box; + z-index: 2147483645; +} + +#tarteaucitronAlertBig #tarteaucitronDisclaimerAlert, +#tarteaucitronAlertBig #tarteaucitronDisclaimerAlert b { + font: 15px verdana; + color: #fff; +} + +#tarteaucitronAlertBig #tarteaucitronDisclaimerAlert b { + font-weight: 700; +} + +#tarteaucitronAlertBig #tarteaucitronCloseAlert, #tarteaucitronAlertBig #tarteaucitronPersonalize { + background: #008300; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 16px; + padding: 5px 10px; + text-decoration: none; + margin-left: 7px; +} + +#tarteaucitronAlertBig #tarteaucitronCloseAlert { + background: #fff; + color: #333; + font-size: 13px; + margin-bottom: 3px; + margin-left: 7px; + padding: 4px 10px; +} + +#tarteaucitronPercentage { + background: #0A0; + box-shadow: 0 0 2px #fff, 0 1px 2px #555; + height: 5px; + left: 0; + position: fixed; + width: 0; + z-index: 2147483644; +} + +/*** + * Small alert + */ +#tarteaucitronAlertSmall { + background: #333; + bottom: 0; + display: none; + padding: 0; + position: fixed; + right: 0; + text-align: center; + width: auto; + z-index: 2147483646; +} + +#tarteaucitronAlertSmall #tarteaucitronManager { + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 11px !important; + padding: 8px 10px 8px; +} + +#tarteaucitronAlertSmall #tarteaucitronManager:hover { + background: rgba(255, 255, 255, 0.05); +} + +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot { + background-color: gray; + border-radius: 5px; + display: block; + height: 8px; + margin-bottom: 1px; + margin-top: 5px; + overflow: hidden; + width: 100%; +} + +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot #tarteaucitronDotGreen, +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot #tarteaucitronDotYellow, +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot #tarteaucitronDotRed { + display: block; + float: left; + height: 100%; + width: 0%; +} + +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot #tarteaucitronDotGreen { + background-color: #1B870B; +} + +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot #tarteaucitronDotYellow { + background-color: #FBDA26; +} + +#tarteaucitronAlertSmall #tarteaucitronManager #tarteaucitronDot #tarteaucitronDotRed { + background-color: #9C1A1A; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesNumber { + background: rgba(255, 255, 255, 0.2); + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 30px; + padding: 0px 10px; + vertical-align: top; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesNumber:hover { + background: rgba(255, 255, 255, 0.3); +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer { + display: none; + max-height: 70%; + max-width: 500px; + position: fixed; + right: 0; + width: 100%; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList { + background: #fff; + border: 2px solid #333; + color: #333; + font-size: 11px; + height: auto; + overflow: auto; + text-align: left; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList b { + color: #333; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesTitle { + background: #333; + margin-top: 21px; + padding: 13px 0 9px 13px; + text-align: left; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesTitle b { + color: #fff; + font-size: 16px; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronCookiesListMain { + background: rgba(51, 51, 51, 0.1); + padding: 7px 5px 10px; + word-wrap: break-word; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronCookiesListMain:hover { + background: rgba(51, 51, 51, 0.2); +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronCookiesListMain a { + color: #333; + text-decoration: none; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronCookiesListMain .tarteaucitronCookiesListLeft { + display: inline-block; + width: 50%; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronCookiesListMain .tarteaucitronCookiesListLeft a b { + color: darkred; +} + +#tarteaucitronAlertSmall #tarteaucitronCookiesListContainer #tarteaucitronCookiesList .tarteaucitronCookiesListMain .tarteaucitronCookiesListRight { + color: #333; + display: inline-block; + font-size: 11px; + margin-left: 10%; + vertical-align: top; + width: 30%; +} + +/*** + * Fallback activate link + */ +.tac_activate { + background: #333; + color: #fff; + display: table; + font-size: 12px; + height: 100%; + line-height: initial; + margin: auto; + text-align: center; + width: 100%; +} + +.tac_float { + display: table-cell; + text-align: center; + vertical-align: middle; +} + +.tac_activate .tac_float b { + color: #fff; +} + +.tac_activate .tac_float .tarteaucitronAllow { + background-color: #1B870B; + display: inline-block; +} + +/*** + * CSS for services + */ +ins.ferank-publicite, ins.adsbygoogle { + text-decoration: none; +} + +div.amazon_product { + height:240px; + width:120px; +} \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.cs.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.cs.js new file mode 100644 index 0000000..5878e51 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.cs.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Ahoj! Tato stránka je transparetní a umožňuje ti si přímo vybrat, jaké služby třetích stran chceš povolit.", + "adblock_call": "Pro úpravu osobních preferencí si, prosím, vypni adblock.", + "reload": "Načíst stránku znovu", + + "alertBigScroll": "Pokračováním ve scrollování,", + "alertBigClick": "Pokud pokračujete v brouzdání našich stránek,", + "alertBig": "povolujete všechny služby třetích stran.", + + "alertBigPrivacy": "Tato stránka využívá cookies a dává ti na výběr, co chceš aktivovat", + "alertSmall": "Spravovat služby", + "personalize": "Přizpůsobit", + "acceptAll": "OK, přijmout vše", + "close": "Zavřít", + + "all": "Nastavení všech služeb", + + "info": "Chrání tvé soukromí", + "disclaimer": "Povolením těchto služeb třetích stran, přijímáš jejich cookies, jež jsou nezbytné pro řádné fungování jejich technologií.", + "allow": "Povolit", + "deny": "Zamítnout", + "noCookie": "Tato služba nepoužívá cookies.", + "useCookie": "Tato služba může nainstalovat", + "useCookieCurrent": "Tato služba nainstalovala", + "useNoCookie": "Tato služba nenainstalovala žádné cookies.", + "more": "Dozvědět se více", + "source": "Zobrazit oficiální stránku", + "credit": "Správce cookies od tarteaucitron.js", + + "fallback": "je vypnutý.", + + "ads": { + "title": "Reklamní síť", + "details": "Prodejem reklamních ploch na této stránce mohou reklamní sítě vydělávat peníze." + }, + "analytic": { + "title": "Statistika návštěvnosti", + "details": "Služby pro analýzu návštěvníků slouží k vytvoření užitečných statistik návštěvnosti. Ty zase slouží ke zlepšení stránky." + }, + "social": { + "title": "Sociální sítě", + "details": "Sociální sítě mohou usnadnit práci se stránkou a pomáhají jí prosadit se pomocí sdílení." + }, + "video": { + "title": "Videa", + "details": "Video-hostingové služby pomáhají přidat na stránku bohaté mediální prvky." + }, + "comment": { + "title": "Komentáře", + "details": "Správce komentářů zajišťují vyplňování komentářů a bojují proti šíření spamu." + }, + "support": { + "title": "Podpora", + "details": "Služby podpory ti pomáhají spojit se s týmem stojícím za stránkou a umožňují ti vyjádřit se k jejím nedostatkům." + }, + "api": { + "title": "API", + "details": "API slouží k načtění skriptů: geolokace, vyhledávačů, překladů, ..." + }, + "other": { + "title": "Jiný", + "details": "Služby pro zobrazení webového obsahu." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.de.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.de.js new file mode 100644 index 0000000..9674de7 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.de.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Hallo! Diese Seite ist transparent und lässt Ihnen die Wahl der externen Services, die aktiviert werden dürfen.", + "adblock_call": "Bitte deaktivieren Sie Ihren 'Werbeblocker' um Konfigurieren zu können.", + "reload": "Seite neu laden", + + "alertBigScroll": "Durch die fortgesetzte blättern,", + "alertBigClick": "Wenn Sie diese Webseite benutzen,", + "alertBig": "stimmen Sie der Benutzung von externen Diensten zu", + + "alertBigPrivacy": "Diese Webseite verwendet 'Cookies' und ermöglicht dadurch Kontrolle, welche Dienste benutzt werden dürfen", + "alertSmall": "Service-Kontrolle", + "personalize": "Personalisieren", + "acceptAll": "OK, akzeptiere alles", + "close": "Beenden", + + "all": "Präferenz für alle Dienste", + + "info": "Schutz der Privatsphäre", + "disclaimer": "Wenn Sie diese Dienste nutzen, erlauben Sie deren 'Cookies' und Tracking-Funktionen, die zu ihrer ordnungsgemäßen Funktion notwendig sind.", + "allow": "Erlauben", + "deny": "Ablehnen", + "noCookie": "Dieser Dienst nutzt keine 'Cookies'.", + "useCookie": "Dieser Dienst kann installieren", + "useCookieCurrent": "Dieser Dienst hat installiert", + "useNoCookie": "Dieser Dienst hat keine 'Cookies' installiert.", + "more": "Weiter lesen", + "source": "Zur offiziellen Webseite", + "credit": "Cookies manager von tarteaucitron.js", + + "fallback": "ist deaktiviert.", + + "ads": { + "title": "Anzeigen Netzwerke", + "details": "Anzeigen Netzwerke können mit dem Verkauf von Werbeplatzierungen auf der Seite Einnahmen erhalten." + }, + "analytic": { + "title": "Besucher Zähldienste", + "details": "Die verwendeten Besucher Zähldienste generieren Statistiken die dabei helfen, die Seite zu verbessern." + }, + "social": { + "title": "Soziale Netzwerke", + "details": "Soziale Netzwerke können die Benutzbarkeit der Seite verbessern und ihren Bekanntheitsgrad erhöhen." + }, + "video": { + "title": "Videos", + "details": "Video Platformen erlauben Videoinhalte einzublenden und die Sichtbarkeit der Seite zu erhöhen." + }, + "comment": { + "title": "Kommentare", + "details": "Kommentar Manager erleichtern die Organisation von Kommentaren und helfen dabei Spam zu verhindern." + }, + "support": { + "title": "Support", + "details": "Support Dienste erlauben es die Urheber der Seite zu kontaktieren und sie zu verbessern." + }, + "api": { + "title": "APIs", + "details": "APIs werden benutzt um Skripte zu laden, wie: Geolokalisation, Suchmaschinen, Übersetzungen, ..." + }, + "other": { + "title": "Andere", + "details": "Dienste zum Anzeigen von Web-Inhalten." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.en.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.en.js new file mode 100644 index 0000000..60f409b --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.en.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Hello! This site is transparent and lets you chose the 3rd party services you want to allow.", + "adblock_call": "Please disable your adblocker to start customizing.", + "reload": "Refresh the page", + + "alertBigScroll": "By continuing to scroll,", + "alertBigClick": "If you continue to browse this website,", + "alertBig": "you are allowing all third-party services", + + "alertBigPrivacy": "This site uses cookies and gives you control over what you want to activate", + "alertSmall": "Manage services", + "personalize": "Personalize", + "acceptAll": "OK, accept all", + "close": "Close", + + "all": "Preference for all services", + + "info": "Protecting your privacy", + "disclaimer": "By allowing these third party services, you accept their cookies and the use of tracking technologies necessary for their proper functioning.", + "allow": "Allow", + "deny": "Deny", + "noCookie": "This service does not use cookie.", + "useCookie": "This service can install", + "useCookieCurrent": "This service has installed", + "useNoCookie": "This service has not installed any cookie.", + "more": "Read more", + "source": "View the official website", + "credit": "Cookies manager by tarteaucitron.js", + + "fallback": "is disabled.", + + "ads": { + "title": "Advertising network", + "details": "Ad networks can generate revenue by selling advertising space on the site." + }, + "analytic": { + "title": "Audience measurement", + "details": "The audience measurement services used to generate useful statistics attendance to improve the site." + }, + "social": { + "title": "Social networks", + "details": "Social networks can improve the usability of the site and help to promote it via the shares." + }, + "video": { + "title": "Videos", + "details": "Video sharing services help to add rich media on the site and increase its visibility." + }, + "comment": { + "title": "Comments", + "details": "Comments managers facilitate the filing of comments and fight against spam." + }, + "support": { + "title": "Support", + "details": "Support services allow you to get in touch with the site team and help to improve it." + }, + "api": { + "title": "APIs", + "details": "APIs are used to load scripts: geolocation, search engines, translations, ..." + }, + "other": { + "title": "Other", + "details": "Services to display web content." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.es.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.es.js new file mode 100644 index 0000000..da3a91d --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.es.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Hola! Este sitio web es transparente y le da la opción de activar los servicios de terceros.", + "adblock_call": "Por favor deshabilite su AdBlocker para comenzar a personalizar.", + "reload": "Actualizar esta página", + + "alertBigScroll": "Al continuar para desplazarse,", + "alertBigClick": "Si continuas navegando por este sitio web,", + "alertBig": "estar permitiendo servicios terceros", + + "alertBigPrivacy": "Este sitio web usa cookies y te permite controlar lo que deseas activar", + "alertSmall": "Gestionar servicios", + "personalize": "Personalizar", + "acceptAll": "OK, aceptar todas", + "close": "Cerrar", + + "all": "Preference for all services", + + "info": "Protegiendo tu privacidad", + "disclaimer": "Aceptando estos servicios terceros, estas aceptando sus cookies y el uso de tecnologías de rastreo necesarias para su correcto funcionamiento.", + "allow": "Permitir", + "deny": "Denegar", + "noCookie": "Este servicio no usa cookie.", + "useCookie": "Este servicio puede instalar", + "useCookieCurrent": "Este servicio ha instalado", + "useNoCookie": "Este servicio no ha instalado ninguna cookie.", + "more": "Leer más", + "source": "Ver sitio web oficial", + "credit": "Gestor de cookies realizada por tarteaucitron.js", + + "fallback": "esta deshabilitado.", + + "ads": { + "title": "Red de publicidad", + "details": "Las redes publicitarias pueden generar ingresos mediante la venta de espacios publicitarios en el sitio." + }, + "analytic": { + "title": "Mediciión de audiencia", + "details": "Los servicios de medición de audiencia se usan para generar asistencia estadísticas útiles para mejorar el sitio." + }, + "social": { + "title": "Redes sociales", + "details": "Las redes sociales pueden aumentar la usabilidad del sitio web y ayudar a promoverlo a través de la contribución." + }, + "video": { + "title": "Videos", + "details": "Los servicios para compartir videos ayudan a añadir contenido enriquecido en el sitio web y aumentar su visibilidad." + }, + "comment": { + "title": "Comentarios", + "details": "El gestor de comentarios facilita la clasificación de comentarios y luchar contra spam." + }, + "support": { + "title": "Soporte", + "details": "Los servicios de soporte te permiten contactar con el sitio web y ayudar a mejorarlo." + }, + "api": { + "title": "APIs", + "details": "APIs se utilizan para cargar scripts: geolocalización, motor de búsqueda, traducciones, ..." + }, + "other": { + "title": "Otro", + "details": "Servicios para mostrar contenido web." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.fr.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.fr.js new file mode 100644 index 0000000..f86b3dd --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.fr.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Bonjour! Ce site joue la transparence et vous donne le choix des services tiers à activer.", + "adblock_call": "Merci de désactiver votre adblocker pour commencer la personnalisation.", + "reload": "Recharger la page", + + "alertBigScroll": "En continuant de défiler,", + "alertBigClick": "En poursuivant votre navigation,", + "alertBig": "vous acceptez l'utilisation de services tiers pouvant installer des cookies", + + "alertBigPrivacy": "Ce site utilise des cookies et vous donne le contrôle sur ce que vous souhaitez activer", + "alertSmall": "Gestion des services", + "acceptAll": "OK, tout accepter", + "personalize": "Personnaliser", + "close": "Fermer", + + "all": "Préférence pour tous les services", + + "info": "Protection de votre vie privée", + "disclaimer": "En autorisant ces services tiers, vous acceptez le dépôt et la lecture de cookies et l'utilisation de technologies de suivi nécessaires à leur bon fonctionnement.", + "allow": "Autoriser", + "deny": "Interdire", + "noCookie": "Ce service ne dépose aucun cookie.", + "useCookie": "Ce service peut déposer", + "useCookieCurrent": "Ce service a déposé", + "useNoCookie": "Ce service n'a déposé aucun cookie.", + "more": "En savoir plus", + "source": "Voir le site officiel", + "credit": "Gestion des cookies par tarteaucitron.js", + + "fallback": "est désactivé.", + + "ads": { + "title": "Régies publicitaires", + "details": "Les régies publicitaires permettent de générer des revenus en commercialisant les espaces publicitaires du site." + }, + "analytic": { + "title": "Mesure d'audience", + "details": "Les services de mesure d'audience permettent de générer des statistiques de fréquentation utiles à l'amélioration du site." + }, + "social": { + "title": "Réseaux sociaux", + "details": "Les réseaux sociaux permettent d'améliorer la convivialité du site et aident à sa promotion via les partages." + }, + "video": { + "title": "Vidéos", + "details": "Les services de partage de vidéo permettent d'enrichir le site de contenu multimédia et augmentent sa visibilité." + }, + "comment": { + "title": "Commentaires", + "details": "Les gestionnaires de commentaires facilitent le dépôt de vos commentaires et luttent contre le spam." + }, + "support": { + "title": "Support", + "details": "Les services de support vous permettent d'entrer en contact avec l'équipe du site et d'aider à son amélioration." + }, + "api": { + "title": "APIs", + "details": "Les APIs permettent de charger des scripts : géolocalisation, moteurs de recherche, traductions, ..." + }, + "other": { + "title": "Autre", + "details": "Services visant à afficher du contenu web." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.it.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.it.js new file mode 100644 index 0000000..fa211f4 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.it.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Benvenuto! Questo sito ti permette di attivare i servizi di terzi di tua scelta.", + "adblock_call": "Disabilita il tuo adblocker per iniziare la navigazione.", + "reload": "Aggiorna la pagina", + + "alertBigScroll": "Continuando a scorrere,", + "alertBigClick": "Continuando a navigare nel sito,", + "alertBig": "autorizzi l’utilizzo dei cookies inviati da domini di terze parti", + + "alertBigPrivacy": "Questo sito fa uso di cookies e ti consente di decidere se accettarli o rifiutarli", + "alertSmall": "Gestione dei servizi", + "acceptAll": "Ok, accetta tutto", + "personalize": "Personalizza", + "close": "Chiudi", + + "all": "Preferenze per tutti i servizi", + + "info": "Tutela della privacy", + "disclaimer": "Abilitando l'uso dei servizi di terze parti, accetti la ricezione dei cookies e l'uso delle tecnologie analitici necessarie al loro funzionamento.", + "allow": "Consenti", + "deny": "Blocca", + "noCookie": "Questo servizio non invia nessun cookie", + "useCookie": "Questo servizio puo' inviare", + "useCookieCurrent": "Questo servizio ha inviato", + "useNoCookie": "Questo servizio non ha inviato nessun cookie", + "more": "Saperne di più", + "source": "Vai al sito ufficiale", + "credit": "Gestione dei cookies da tarteaucitron.js", + + "fallback": "è disattivato", + + "ads": { + "title": "Regie pubblicitarie", + "details": "Le regie pubblicitarie producono redditi gestendo la commercializzazione degli spazi del sito dedicati alle campagne pubblicitarie" + }, + "analytic": { + "title": "Misura del pubblico", + "details": "I servizi di misura del pubblico permettono di raccogliere le statistiche utili al miglioramento del sito" + }, + "social": { + "title": "Reti sociali", + "details": "Le reti sociali permettono di migliorare l'aspetto conviviale del sito e di sviluppare la condivisione dei contenuti da parte degli utenti a fini promozionali." + }, + "video": { + "title": "Video", + "details": "I servizi di condivisione di video permettono di arricchire il sito di contenuti multimediali e di aumentare la sua visibilità" + }, + "comment": { + "title": "Commenti", + "details": "La gestione dei commenti utente aiuta a gestire la pubblicazione dei commenti e a lottare contro lo spamming" + }, + "support": { + "title": "Supporto", + "details": "I servizi di supporto ti consentono di contattare la team del sito e di contribuire al suo miglioramento" + }, + "api": { + "title": "API", + "details": "Le API permettono di implementare script diversi : geolocalizzazione, motori di ricerca, traduttori..." + }, + "other": { + "title": "Altro", + "details": "Servizi per visualizzare contenuti web." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.pl.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.pl.js new file mode 100644 index 0000000..31db3c6 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.pl.js @@ -0,0 +1,66 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Witaj! Ta witryna oferuje przejrzystosc i daje mozliwosc wyboru aktywacji uslug zewnetrznych.", + "adblock_call": "Prosze wylaczyc adblocker aby rozpoczac dostosowanie do potrzeb uzytkownika.", + "reload": "Odswiez strone", + + "alertBigScroll": "Poprzez kontynuowanie przewijania,", + "alertBigClick": "Pozostajac na tej stronie", + "alertBig": "zgadzasz sie na korzystanie ze wszystkich zewnetrzynych uslug", + + "alertBigPrivacy": "Ta witryna używa plików cookie i pozwala kontrolować ich aktywacje", + "alertSmall": "Zarządzanie usługami", + "personalize": "Personalizacja", + "acceptAll": "OK, akceptuję wszystko", + "close": "zamknij", + + "all": "Preferencja dla wszystkich usług", + + "info": "Ochrona prywatności", + "disclaimer": "Zgadzajac sie na korzystanie z uslug zewnetrznych , akceptuje ich pliki cookies oraz wykorzystanie technologii niezbędnych do ich funkcjonowania.", + "allow": "Zezwalaj", + "deny": "Odmów", + "noCookie": "Ta usługa nie korzysta z plików cookie.", + "useCookie": "Ta usługa może zainstalować pliki cookie", + "useCookieCurrent": "Ta usługa zainstalowala plikie cookie", + "useNoCookie": "Ta usługa nie zainstalowala żadnego pliku cookie.", + "more": "Więcej informacji", + "source": "Zobacz oficjalną stronę internetowa", + "credit": "Cookies menadżer z tarteaucitron.js", + + "fallback": "jest nieaktywna.", + + "ads": { + "title": "Sieć reklamowa", + "details": "Sieci reklamowe mogą generować przychody ze sprzedaży powierzchni reklamowej na stronie." + }, + "analytic": { + "title": "Pomiar ogladalnosci", + "details": "Usługi pomiaru oglądalności wykorzystywane sa do generowania przydatnych statystyk potrzebnych w doskonaleniu strony." + }, + "social": { + "title": "Portale społecznościowe", + "details": "Sieci społecznościowe mogą poprawić użyteczność serwisu i pomóc w promocji za pośrednictwem propagacji strony." + }, + "video": { + "title": "Filmy", + "details": "Usługa udostępniania wideo pomoże dodać multimedia do strony i zwiększyć jej ogladalność." + }, + "comment": { + "title": "Komentarze", + "details": "Zarządzanie komentarzami ułatwia komentowanie i zwalcza spam." + }, + "support": { + "title": "Pomoc", + "details": "Usługa pomocy technicznej pozwala, skontaktować się z administratorem witryny i pomaga ją udoskonalić." + }, + "api": { + "title": "APIs", + "details": "APIs służą do ładowania skryptów: geolokalizacji, wyszukiwarek, tłumaczenia, ..." + }, + "other": { + "title": "Inny", + "details": "Usługi do wyświetlania treści internetowych." + } + +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.pt.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.pt.js new file mode 100644 index 0000000..239dcc6 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.pt.js @@ -0,0 +1,61 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Olá! Em uma açao de transparencia, este site lhe dá a opção de quais serviços terceiros deseje ativar.", + "adblock_call": "Por favor, desative seu bloqueador de publicidades para poder customizar.", + "reload": "Atualizar esta página", + + "alertBigScroll": "Ao continuar a rolar,", + "alertBigClick": "Se você continuar a navegaçao neste site,", + "alertBig": "você estará aceitando todos os serviços terceiros", + + "alertBigPrivacy": "Esse site utiliza cookies and lhe dá controle sobre o que você quer ativar", + "alertSmall": "Gerenciar serviços", + "personalize": "Personalizar", + "acceptAll": "OK, aceitar tudo", + "close": "Fechar", + "all": "Definições dos serviços", + "info": "Proteger sua privacidade", + "disclaimer": "Ao aceitar os serviços terceiros, você aceita o uso de cookies em conjunto de tecnologias de rastreamento que lhe são necessárias para funcionar", + "allow": "Autorizar", + "deny": "Recusar", + "noCookie": "Este serviço não usa cookies.", + "useCookie": "Este serviço pode instalar", + "useCookieCurrent": "Este serviço instalou", + "useNoCookie": "Este serviço não instalou nenhum cookie.", + "more": "Ler mais", + "source": "Ver o site oficial", + "credit": "Gerenciador de cookies por tarteaucitron.js", + "fallback": "está desativado.", + "ads": { + "title": "Rede de anúncios", + "details": "As redes de anúncios podem gerar receitas com a venda de espaço publicitário no site." + }, + "analytic": { + "title": "Medição de audiência", + "details": "Serviços de medição de audiência usados para gerar estatísticas no intuito de melhorar o site." + }, + "social": { + "title": "Rede sociais", + "details": "Rede sociais podem ameliorar o utilização do site e ajudar a promove-lo via compartilhamentos." + }, + "video": { + "title": "Vídeos", + "details": "Video sharing services help to add rich media on the site and increase its visibility." + }, + "comment": { + "title": "Comentários", + "details": "Gerenciadores de comentários facilitam o sistema de comentários e lutam contra o spam." + }, + "support": { + "title": "Suporte", + "details": "Serviços de suporte lhe ajudam a entrar em contato com a equipe de suporte." + }, + "api": { + "title": "APIs", + "details": "APIs são usadas para carregar scripts: geolocalização, motores de pesquisa, traduções, ..." + }, + "other": { + "title": "De outros", + "details": "Serviços para exibir conteúdo da web." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.ru.js b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.ru.js new file mode 100644 index 0000000..54c9651 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/lang/tarteaucitron.ru.js @@ -0,0 +1,65 @@ +/*global tarteaucitron */ +tarteaucitron.lang = { + "adblock": "Привет! Этот сайт совершенно открытый и позволяет вам выбрать сервисы третьих лиц, которым вы хотите дать доступ.", + "adblock_call": "Пожалуйста дезактивируйте АдБлокер чтобы начать настройку.", + "reload": "Перезагрузите страницу", + + "alertBigScroll": "Продолжая прокрутки", + "alertBigClick": "Если вы продолжаете использовать сайт", + "alertBig": "вы позволяете сервисы третьих лиц", + + "alertBigPrivacy": "Этот сайт использует кукис и позволяет вам контролировать сервисы которые вы хотите активировать", + "alertSmall": "Настройка сервисов", + "personalize": "Персонализировать", + "acceptAll": "Ок, все активировать", + "close": "Закрыть", + + "all": "Преференция всем сервисам", + + "info": "Защитить вашу конфиденциальность", + "disclaimer": "Активирование сервисов третьих лиц позволяет использование их кукис и технолоний отслеживания необходимых для их функционирования", + "allow": "Позролить", + "deny": "Не позволить", + "noCookie": "Этот сервис не использует кукис.", + "useCookie": "Этот сервис может быть инсталирован", + "useCookieCurrent": "Этот сервис инсталирован", + "useNoCookie": "Этот сервис не использует кукис.", + "more": "Подробнее", + "source": "Посетите официальный сайт", + "credit": "Кукис манаджер tarteaucitron.js", + + "fallback": "Деактивирован.", + + "ads": { + "title": "Рекламная сеть", + "details": "Мы позволяем вам аренду нашей рекламной сети." + }, + "analytic": { + "title": "Измерение аудиенции", + "details": "Измерение аудиенции сайта для статистики помогают улучшить предлагаемый сервис." + }, + "social": { + "title": "Социальная сеть", + "details": "Социальная сеть сайтов помогает улучшить предлагаемый сервис через обмен информации." + }, + "video": { + "title": "Видео", + "details": "Обмен видео информации позволяет улучшить сервис и увеличит траффик сайта." + }, + "comment": { + "title": "Комментарии", + "details": "Манаджер комментариев позволяет обмен информации и борьбу со спамом." + }, + "support": { + "title": "Помощь", + "details": "Помощь позволяет вам контактировать напрямую сайт манаджер и улучшить предлагаемый сервис." + }, + "api": { + "title": "АПИ", + "details": "АПИ используются для загрузки скриптов; геолокация, поисковый мотор и переводы..." + }, + "other": { + "title": "Другие", + "details": "Службы для отображения веб-контента." + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/tarteaucitron.js b/docs/pages/assets/js/tarteaucitron/tarteaucitron.js new file mode 100644 index 0000000..57f818e --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/tarteaucitron.js @@ -0,0 +1,1265 @@ +/*jslint browser: true, evil: true */ + +// define correct path for files inclusion +var scripts = document.getElementsByTagName('script'), + path = scripts[scripts.length - 1].src.split('?')[0], + cdn = path.split('/').slice(0, -1).join('/') + '/', + alreadyLaunch = (alreadyLaunch === undefined) ? 0 : alreadyLaunch, + tarteaucitronForceLanguage = (tarteaucitronForceLanguage === undefined) ? '' : tarteaucitronForceLanguage, + tarteaucitronProLoadServices, + tarteaucitronNoAdBlocker = false; + +var tarteaucitron = { + "version": 323, + "cdn": cdn, + "user": {}, + "lang": {}, + "services": {}, + "added": [], + "idprocessed": [], + "state": [], + "launch": [], + "parameters": {}, + "isAjax": false, + "reloadThePage": false, + "init": function (params) { + "use strict"; + var origOpen; + + tarteaucitron.parameters = params; + if (alreadyLaunch === 0) { + alreadyLaunch = 1; + if (window.addEventListener) { + window.addEventListener("load", function () { + tarteaucitron.load(); + tarteaucitron.fallback(['tarteaucitronOpenPanel'], function (elem) { + elem.addEventListener("click", function (event) { + tarteaucitron.userInterface.openPanel(); + event.preventDefault(); + }, false); + }, true); + }, false); + window.addEventListener("scroll", function () { + var scrollPos = window.pageYOffset || document.documentElement.scrollTop, + heightPosition; + if (document.getElementById('tarteaucitronAlertBig') !== null && !tarteaucitron.highPrivacy) { + if (document.getElementById('tarteaucitronAlertBig').style.display === 'block') { + heightPosition = document.getElementById('tarteaucitronAlertBig').offsetHeight + 'px'; + + if (scrollPos > (screen.height * 2)) { + tarteaucitron.userInterface.respondAll(true); + } else if (scrollPos > (screen.height / 2)) { + document.getElementById('tarteaucitronDisclaimerAlert').innerHTML = '' + tarteaucitron.lang.alertBigScroll + ' ' + tarteaucitron.lang.alertBig; + } + + if (tarteaucitron.orientation === 'top') { + document.getElementById('tarteaucitronPercentage').style.top = heightPosition; + } else { + document.getElementById('tarteaucitronPercentage').style.bottom = heightPosition; + } + document.getElementById('tarteaucitronPercentage').style.width = ((100 / (screen.height * 2)) * scrollPos) + '%'; + } + } + }, false); + window.addEventListener("keydown", function (evt) { + if (evt.keyCode === 27) { + tarteaucitron.userInterface.closePanel(); + } + }, false); + window.addEventListener("hashchange", function () { + if (document.location.hash === tarteaucitron.hashtag && tarteaucitron.hashtag !== '') { + tarteaucitron.userInterface.openPanel(); + } + }, false); + window.addEventListener("resize", function () { + if (document.getElementById('tarteaucitron') !== null) { + if (document.getElementById('tarteaucitron').style.display === 'block') { + tarteaucitron.userInterface.jsSizing('main'); + } + } + + if (document.getElementById('tarteaucitronCookiesListContainer') !== null) { + if (document.getElementById('tarteaucitronCookiesListContainer').style.display === 'block') { + tarteaucitron.userInterface.jsSizing('cookie'); + } + } + }, false); + } else { + window.attachEvent("onload", function () { + tarteaucitron.load(); + tarteaucitron.fallback(['tarteaucitronOpenPanel'], function (elem) { + elem.attachEvent("onclick", function (event) { + tarteaucitron.userInterface.openPanel(); + event.preventDefault(); + }); + }, true); + }); + window.attachEvent("onscroll", function () { + var scrollPos = window.pageYOffset || document.documentElement.scrollTop, + heightPosition; + if (document.getElementById('tarteaucitronAlertBig') !== null && !tarteaucitron.highPrivacy) { + if (document.getElementById('tarteaucitronAlertBig').style.display === 'block') { + heightPosition = document.getElementById('tarteaucitronAlertBig').offsetHeight + 'px'; + + if (scrollPos > (screen.height * 2)) { + tarteaucitron.userInterface.respondAll(true); + } else if (scrollPos > (screen.height / 2)) { + document.getElementById('tarteaucitronDisclaimerAlert').innerHTML = '' + tarteaucitron.lang.alertBigScroll + ' ' + tarteaucitron.lang.alertBig; + } + if (tarteaucitron.orientation === 'top') { + document.getElementById('tarteaucitronPercentage').style.top = heightPosition; + } else { + document.getElementById('tarteaucitronPercentage').style.bottom = heightPosition; + } + document.getElementById('tarteaucitronPercentage').style.width = ((100 / (screen.height * 2)) * scrollPos) + '%'; + } + } + }); + window.attachEvent("onkeydown", function (evt) { + if (evt.keyCode === 27) { + tarteaucitron.userInterface.closePanel(); + } + }); + window.attachEvent("onhashchange", function () { + if (document.location.hash === tarteaucitron.hashtag && tarteaucitron.hashtag !== '') { + tarteaucitron.userInterface.openPanel(); + } + }); + window.attachEvent("onresize", function () { + if (document.getElementById('tarteaucitron') !== null) { + if (document.getElementById('tarteaucitron').style.display === 'block') { + tarteaucitron.userInterface.jsSizing('main'); + } + } + + if (document.getElementById('tarteaucitronCookiesListContainer') !== null) { + if (document.getElementById('tarteaucitronCookiesListContainer').style.display === 'block') { + tarteaucitron.userInterface.jsSizing('cookie'); + } + } + }); + } + + if (typeof XMLHttpRequest !== 'undefined') { + origOpen = XMLHttpRequest.prototype.open; + XMLHttpRequest.prototype.open = function () { + + if (window.addEventListener) { + this.addEventListener("load", function () { + if (typeof tarteaucitronProLoadServices === 'function') { + tarteaucitronProLoadServices(); + } + }, false); + } else if (typeof this.attachEvent !== 'undefined') { + this.attachEvent("onload", function () { + if (typeof tarteaucitronProLoadServices === 'function') { + tarteaucitronProLoadServices(); + } + }); + } else { + if (typeof tarteaucitronProLoadServices === 'function') { + setTimeout(tarteaucitronProLoadServices, 1000); + } + } + + try { + origOpen.apply(this, arguments); + } catch (err) {} + }; + } + } + }, + "load": function () { + "use strict"; + var cdn = tarteaucitron.cdn, + language = tarteaucitron.getLanguage(), + pathToLang = cdn + 'lang/tarteaucitron.' + language + '.js?v=' + tarteaucitron.version, + pathToServices = cdn + 'tarteaucitron.services.js?v=' + tarteaucitron.version, + linkElement = document.createElement('link'), + defaults = { + "adblocker": false, + "hashtag": '#tarteaucitron', + "highPrivacy": false, + "orientation": "top", + "removeCredit": false, + "showAlertSmall": true, + "cookieslist": true + }, + params = tarteaucitron.parameters; + + // Step 0: get params + if (params !== undefined) { + tarteaucitron.extend(defaults, params); + } + + // global + tarteaucitron.orientation = defaults.orientation; + tarteaucitron.hashtag = defaults.hashtag; + tarteaucitron.highPrivacy = defaults.highPrivacy; + + // Step 1: load css + linkElement.rel = 'stylesheet'; + linkElement.type = 'text/css'; + linkElement.href = cdn + 'css/tarteaucitron.css?v=' + tarteaucitron.version; + document.getElementsByTagName('head')[0].appendChild(linkElement); + + // Step 2: load language and services + tarteaucitron.addScript(pathToLang, '', function () { + tarteaucitron.addScript(pathToServices, '', function () { + + var body = document.body, + div = document.createElement('div'), + html = '', + index, + orientation = 'Top', + cat = ['ads', 'analytic', 'api', 'comment', 'social', 'support', 'video', 'other'], + i; + + cat = cat.sort(function (a, b) { + if (tarteaucitron.lang[a].title > tarteaucitron.lang[b].title) { return 1; } + if (tarteaucitron.lang[a].title < tarteaucitron.lang[b].title) { return -1; } + return 0; + }); + + // Step 3: prepare the html + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.close; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.all + ''; + html += '
'; + html += '
'; + html += '
'; + html += ' ✓ ' + tarteaucitron.lang.allow; + html += '
'; + html += '
'; + html += ' ✗ ' + tarteaucitron.lang.deny; + html += '
'; + html += '
'; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.disclaimer; + if (defaults.removeCredit === false) { + html += '

'; + html += ' ' + tarteaucitron.lang.credit + ''; + } + html += '
'; + html += '
'; + html += '
'; + for (i = 0; i < cat.length; i += 1) { + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang[cat[i]].title; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang[cat[i]].details; + html += '
'; + html += '
'; + html += '
'; + } + html += '
'; + html += '
'; + html += '
'; + html += '
'; + + if (defaults.orientation === 'bottom') { + orientation = 'Bottom'; + } + + if (defaults.highPrivacy) { + html += '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.alertBigPrivacy; + html += ' '; + html += ' '; + html += ' ' + tarteaucitron.lang.personalize; + html += ' '; + html += '
'; + } else { + html += '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.alertBigClick + ' ' + tarteaucitron.lang.alertBig; + html += ' '; + html += ' '; + html += ' ✓ ' + tarteaucitron.lang.acceptAll; + html += ' '; + html += ' '; + html += ' ' + tarteaucitron.lang.personalize; + html += ' '; + html += '
'; + html += '
'; + } + + if (defaults.showAlertSmall === true) { + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.alertSmall; + html += '
'; + html += ' '; + html += ' '; + html += ' '; + html += '
'; + if (defaults.cookieslist === true) { + html += '
0
'; + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.lang.close; + html += '
'; + html += '
'; + html += ' 0 cookie'; + html += '
'; + html += '
'; + html += '
'; + } else { + html += '
'; + } + html += ''; + } + + tarteaucitron.addScript(tarteaucitron.cdn + 'advertising.js?v=' + tarteaucitron.version, '', function () { + if (tarteaucitronNoAdBlocker === true || defaults.adblocker === false) { + div.id = 'tarteaucitronRoot'; + body.appendChild(div, body); + div.innerHTML = html; + + if (tarteaucitron.job !== undefined) { + tarteaucitron.job = tarteaucitron.cleanArray(tarteaucitron.job); + for (index = 0; index < tarteaucitron.job.length; index += 1) { + tarteaucitron.addService(tarteaucitron.job[index]); + } + } + + tarteaucitron.isAjax = true; + tarteaucitron.job.push = function (id) { + + // ie <9 hack + if (typeof tarteaucitron.job.indexOf === 'undefined') { + tarteaucitron.job.indexOf = function (obj, start) { + var i, + j = this.length; + for (i = (start || 0); i < j; i += 1) { + if (this[i] === obj) { return i; } + } + return -1; + }; + } + + if (tarteaucitron.job.indexOf(id) === -1) { + Array.prototype.push.call(this, id); + } + tarteaucitron.launch[id] = false; + tarteaucitron.addService(id); + }; + + if (document.location.hash === tarteaucitron.hashtag && tarteaucitron.hashtag !== '') { + tarteaucitron.userInterface.openPanel(); + } + + tarteaucitron.cookie.number(); + setInterval(tarteaucitron.cookie.number, 60000); + } + }, defaults.adblocker); + + if (defaults.adblocker === true) { + setTimeout(function () { + if (tarteaucitronNoAdBlocker === false) { + html = '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.adblock + '
'; + html += ' ' + tarteaucitron.lang.adblock_call + ''; + html += '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.reload; + html += ' '; + html += '
'; + html += '
'; + div.id = 'tarteaucitronRoot'; + body.appendChild(div, body); + div.innerHTML = html; + tarteaucitron.pro('!adblocker=true'); + } else { + tarteaucitron.pro('!adblocker=false'); + } + }, 1500); + } + }); + }); + }, + "addService": function (serviceId) { + "use strict"; + var html = '', + s = tarteaucitron.services, + service = s[serviceId], + cookie = tarteaucitron.cookie.read(), + hostname = document.location.hostname, + hostRef = document.referrer.split('/')[2], + isNavigating = (hostRef === hostname) ? true : false, + isAutostart = (!service.needConsent) ? true : false, + isWaiting = (cookie.indexOf(service.key + '=wait') >= 0) ? true : false, + isDenied = (cookie.indexOf(service.key + '=false') >= 0) ? true : false, + isAllowed = (cookie.indexOf(service.key + '=true') >= 0) ? true : false, + isResponded = (cookie.indexOf(service.key + '=false') >= 0 || cookie.indexOf(service.key + '=true') >= 0) ? true : false; + + if (tarteaucitron.added[service.key] !== true) { + tarteaucitron.added[service.key] = true; + + html += '
'; + html += '
'; + html += ' ' + service.name + '
'; + html += '
'; + html += ' '; + html += ' ' + tarteaucitron.lang.more; + html += ' '; + html += ' - '; + html += ' '; + html += ' ' + tarteaucitron.lang.source; + html += ' '; + html += '
'; + html += '
'; + html += '
'; + html += ' ✓ ' + tarteaucitron.lang.allow; + html += '
'; + html += '
'; + html += ' ✗ ' + tarteaucitron.lang.deny; + html += '
'; + html += '
'; + html += '
'; + + tarteaucitron.userInterface.css('tarteaucitronServicesTitle_' + service.type, 'display', 'block'); + + if (document.getElementById('tarteaucitronServices_' + service.type) !== null) { + document.getElementById('tarteaucitronServices_' + service.type).innerHTML += html; + } + + tarteaucitron.userInterface.order(service.type); + } + + // allow by default for non EU + if (isResponded === false && tarteaucitron.user.bypass === true) { + isAllowed = true; + tarteaucitron.cookie.create(service.key, true); + } + + if ((!isResponded && (isAutostart || (isNavigating && isWaiting)) && !tarteaucitron.highPrivacy) || isAllowed) { + if (!isAllowed) { + tarteaucitron.cookie.create(service.key, true); + } + if (tarteaucitron.launch[service.key] !== true) { + tarteaucitron.launch[service.key] = true; + service.js(); + } + tarteaucitron.state[service.key] = true; + tarteaucitron.userInterface.color(service.key, true); + } else if (isDenied) { + if (typeof service.fallback === 'function') { + service.fallback(); + } + tarteaucitron.state[service.key] = false; + tarteaucitron.userInterface.color(service.key, false); + } else if (!isResponded) { + tarteaucitron.cookie.create(service.key, 'wait'); + if (typeof service.fallback === 'function') { + service.fallback(); + } + tarteaucitron.userInterface.color(service.key, 'wait'); + tarteaucitron.userInterface.openAlert(); + } + + tarteaucitron.cookie.checkCount(service.key); + }, + "cleanArray": function cleanArray(arr) { + "use strict"; + var i, + len = arr.length, + out = [], + obj = {}, + s = tarteaucitron.services; + + for (i = 0; i < len; i += 1) { + if (!obj[arr[i]]) { + obj[arr[i]] = {}; + if (tarteaucitron.services[arr[i]] !== undefined) { + out.push(arr[i]); + } + } + } + + out = out.sort(function (a, b) { + if (s[a].type + s[a].key > s[b].type + s[b].key) { return 1; } + if (s[a].type + s[a].key < s[b].type + s[b].key) { return -1; } + return 0; + }); + + return out; + }, + "userInterface": { + "css": function (id, property, value) { + "use strict"; + if (document.getElementById(id) !== null) { + document.getElementById(id).style[property] = value; + } + }, + "respondAll": function (status) { + "use strict"; + var s = tarteaucitron.services, + service, + key, + index = 0; + + for (index = 0; index < tarteaucitron.job.length; index += 1) { + service = s[tarteaucitron.job[index]]; + key = service.key; + if (tarteaucitron.state[key] !== status) { + if (status === false && tarteaucitron.launch[key] === true) { + tarteaucitron.reloadThePage = true; + } + if (tarteaucitron.launch[key] !== true && status === true) { + tarteaucitron.launch[key] = true; + tarteaucitron.services[key].js(); + } + tarteaucitron.state[key] = status; + tarteaucitron.cookie.create(key, status); + tarteaucitron.userInterface.color(key, status); + } + } + }, + "respond": function (el, status) { + "use strict"; + var key = el.id.replace(new RegExp("(Eng[0-9]+|Allow|Deni)ed", "g"), ''); + + // return if same state + if (tarteaucitron.state[key] === status) { + return; + } + + if (status === false && tarteaucitron.launch[key] === true) { + tarteaucitron.reloadThePage = true; + } + + // if not already launched... launch the service + if (status === true) { + if (tarteaucitron.launch[key] !== true) { + tarteaucitron.launch[key] = true; + tarteaucitron.services[key].js(); + } + } + tarteaucitron.state[key] = status; + tarteaucitron.cookie.create(key, status); + tarteaucitron.userInterface.color(key, status); + }, + "color": function (key, status) { + "use strict"; + var gray = '#808080', + greenDark = '#1B870B', + greenLight = '#E6FFE2', + redDark = '#9C1A1A', + redLight = '#FFE2E2', + yellowDark = '#FBDA26', + c = 'tarteaucitron', + nbDenied = 0, + nbPending = 0, + nbAllowed = 0, + sum = tarteaucitron.job.length, + index; + + if (status === true) { + tarteaucitron.userInterface.css(key + 'Line', 'borderLeft', '5px solid ' + greenDark); + tarteaucitron.userInterface.css(key + 'Allowed', 'backgroundColor', greenDark); + tarteaucitron.userInterface.css(key + 'Denied', 'backgroundColor', gray); + } else if (status === false) { + tarteaucitron.userInterface.css(key + 'Line', 'borderLeft', '5px solid ' + redDark); + tarteaucitron.userInterface.css(key + 'Allowed', 'backgroundColor', gray); + tarteaucitron.userInterface.css(key + 'Denied', 'backgroundColor', redDark); + } + + // check if all services are allowed + for (index = 0; index < sum; index += 1) { + if (tarteaucitron.state[tarteaucitron.job[index]] === false) { + nbDenied += 1; + } else if (tarteaucitron.state[tarteaucitron.job[index]] === undefined) { + nbPending += 1; + } else if (tarteaucitron.state[tarteaucitron.job[index]] === true) { + nbAllowed += 1; + } + } + + tarteaucitron.userInterface.css(c + 'DotGreen', 'width', ((100 / sum) * nbAllowed) + '%'); + tarteaucitron.userInterface.css(c + 'DotYellow', 'width', ((100 / sum) * nbPending) + '%'); + tarteaucitron.userInterface.css(c + 'DotRed', 'width', ((100 / sum) * nbDenied) + '%'); + + if (nbDenied === 0 && nbPending === 0) { + tarteaucitron.userInterface.css(c + 'AllAllowed', 'backgroundColor', greenDark); + tarteaucitron.userInterface.css(c + 'AllDenied', 'backgroundColor', gray); + } else if (nbAllowed === 0 && nbPending === 0) { + tarteaucitron.userInterface.css(c + 'AllAllowed', 'backgroundColor', gray); + tarteaucitron.userInterface.css(c + 'AllDenied', 'backgroundColor', redDark); + } else { + tarteaucitron.userInterface.css(c + 'AllAllowed', 'backgroundColor', gray); + tarteaucitron.userInterface.css(c + 'AllDenied', 'backgroundColor', gray); + } + + // close the alert if all service have been reviewed + if (nbPending === 0) { + tarteaucitron.userInterface.closeAlert(); + } + + if (tarteaucitron.services[key].cookies.length > 0 && status === false) { + tarteaucitron.cookie.purge(tarteaucitron.services[key].cookies); + } + + if (status === true) { + if (document.getElementById('tacCL' + key) !== null) { + document.getElementById('tacCL' + key).innerHTML = '...'; + } + setTimeout(function () { + tarteaucitron.cookie.checkCount(key); + }, 2500); + } else { + tarteaucitron.cookie.checkCount(key); + } + }, + "openPanel": function () { + "use strict"; + tarteaucitron.userInterface.css('tarteaucitron', 'display', 'block'); + tarteaucitron.userInterface.css('tarteaucitronBack', 'display', 'block'); + tarteaucitron.userInterface.css('tarteaucitronCookiesListContainer', 'display', 'none'); + tarteaucitron.userInterface.jsSizing('main'); + }, + "closePanel": function () { + "use strict"; + + if (document.location.hash === tarteaucitron.hashtag) { + document.location.hash = ''; + } + tarteaucitron.userInterface.css('tarteaucitron', 'display', 'none'); + tarteaucitron.userInterface.css('tarteaucitronCookiesListContainer', 'display', 'none'); + + tarteaucitron.fallback(['tarteaucitronInfoBox'], function (elem) { + elem.style.display = 'none'; + }, true); + + if (tarteaucitron.reloadThePage === true) { + window.location.reload(); + } else { + tarteaucitron.userInterface.css('tarteaucitronBack', 'display', 'none'); + } + }, + "openAlert": function () { + "use strict"; + var c = 'tarteaucitron'; + tarteaucitron.userInterface.css(c + 'Percentage', 'display', 'block'); + tarteaucitron.userInterface.css(c + 'AlertSmall', 'display', 'none'); + tarteaucitron.userInterface.css(c + 'AlertBig', 'display', 'block'); + }, + "closeAlert": function () { + "use strict"; + var c = 'tarteaucitron'; + tarteaucitron.userInterface.css(c + 'Percentage', 'display', 'none'); + tarteaucitron.userInterface.css(c + 'AlertSmall', 'display', 'block'); + tarteaucitron.userInterface.css(c + 'AlertBig', 'display', 'none'); + tarteaucitron.userInterface.jsSizing('box'); + }, + "toggleCookiesList": function () { + "use strict"; + var div = document.getElementById('tarteaucitronCookiesListContainer'); + + if (div === null) { + return; + } + + if (div.style.display !== 'block') { + tarteaucitron.cookie.number(); + div.style.display = 'block'; + tarteaucitron.userInterface.jsSizing('cookie'); + tarteaucitron.userInterface.css('tarteaucitron', 'display', 'none'); + tarteaucitron.userInterface.css('tarteaucitronBack', 'display', 'block'); + tarteaucitron.fallback(['tarteaucitronInfoBox'], function (elem) { + elem.style.display = 'none'; + }, true); + } else { + div.style.display = 'none'; + tarteaucitron.userInterface.css('tarteaucitron', 'display', 'none'); + tarteaucitron.userInterface.css('tarteaucitronBack', 'display', 'none'); + } + }, + "toggle": function (id, closeClass) { + "use strict"; + var div = document.getElementById(id); + + if (div === null) { + return; + } + + if (closeClass !== undefined) { + tarteaucitron.fallback([closeClass], function (elem) { + if (elem.id !== id) { + elem.style.display = 'none'; + } + }, true); + } + + if (div.style.display !== 'block') { + div.style.display = 'block'; + } else { + div.style.display = 'none'; + } + }, + "order": function (id) { + "use strict"; + var main = document.getElementById('tarteaucitronServices_' + id), + allDivs, + store = [], + i; + + if (main === null) { + return; + } + + allDivs = main.childNodes; + + if (typeof Array.prototype.map === 'function') { + Array.prototype.map.call(main.children, Object).sort(function (a, b) { + if (tarteaucitron.services[a.id.replace(/Line/g, '')].name > tarteaucitron.services[b.id.replace(/Line/g, '')].name) { return 1; } + if (tarteaucitron.services[a.id.replace(/Line/g, '')].name < tarteaucitron.services[b.id.replace(/Line/g, '')].name) { return -1; } + return 0; + }).forEach(function (element) { + main.appendChild(element); + }); + } + }, + "jsSizing": function (type) { + "use strict"; + var scrollbarMarginRight = 10, + scrollbarWidthParent, + scrollbarWidthChild, + servicesHeight, + e = window, + a = 'inner', + windowInnerHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight, + mainTop, + mainHeight, + closeButtonHeight, + headerHeight, + cookiesListHeight, + cookiesCloseHeight, + cookiesTitleHeight, + paddingBox, + alertSmallHeight, + cookiesNumberHeight; + + if (type === 'box') { + if (document.getElementById('tarteaucitronAlertSmall') !== null && document.getElementById('tarteaucitronCookiesNumber') !== null) { + + // reset + tarteaucitron.userInterface.css('tarteaucitronCookiesNumber', 'padding', '0px 10px'); + + // calculate + alertSmallHeight = document.getElementById('tarteaucitronAlertSmall').offsetHeight; + cookiesNumberHeight = document.getElementById('tarteaucitronCookiesNumber').offsetHeight; + paddingBox = (alertSmallHeight - cookiesNumberHeight) / 2; + + // apply + tarteaucitron.userInterface.css('tarteaucitronCookiesNumber', 'padding', paddingBox + 'px 10px'); + } + } else if (type === 'main') { + + // get the real window width for media query + if (window.innerWidth === undefined) { + a = 'client'; + e = document.documentElement || document.body; + } + + // height of the services list container + if (document.getElementById('tarteaucitron') !== null && document.getElementById('tarteaucitronClosePanel') !== null && document.getElementById('tarteaucitronMainLineOffset') !== null) { + + // reset + tarteaucitron.userInterface.css('tarteaucitronScrollbarParent', 'height', 'auto'); + + // calculate + mainHeight = document.getElementById('tarteaucitron').offsetHeight; + closeButtonHeight = document.getElementById('tarteaucitronClosePanel').offsetHeight; + headerHeight = document.getElementById('tarteaucitronMainLineOffset').offsetHeight; + + // apply + servicesHeight = (mainHeight - closeButtonHeight - headerHeight + 1); + tarteaucitron.userInterface.css('tarteaucitronScrollbarParent', 'height', servicesHeight + 'px'); + } + + // align the main allow/deny button depending on scrollbar width + if (document.getElementById('tarteaucitronScrollbarParent') !== null && document.getElementById('tarteaucitronScrollbarChild') !== null) { + + // media query + if (e[a + 'Width'] <= 479) { + tarteaucitron.userInterface.css('tarteaucitronScrollbarAdjust', 'marginLeft', '11px'); + } else if (e[a + 'Width'] <= 767) { + scrollbarMarginRight = 12; + } + + scrollbarWidthParent = document.getElementById('tarteaucitronScrollbarParent').offsetWidth; + scrollbarWidthChild = document.getElementById('tarteaucitronScrollbarChild').offsetWidth; + tarteaucitron.userInterface.css('tarteaucitronScrollbarAdjust', 'marginRight', ((scrollbarWidthParent - scrollbarWidthChild) + scrollbarMarginRight) + 'px'); + } + + // center the main panel + if (document.getElementById('tarteaucitron') !== null) { + + // media query + if (e[a + 'Width'] <= 767) { + mainTop = 0; + } else { + mainTop = ((windowInnerHeight - document.getElementById('tarteaucitron').offsetHeight) / 2) - 21; + } + + // correct + if (mainTop < 0) { + mainTop = 0; + } + + if (document.getElementById('tarteaucitronMainLineOffset') !== null) { + if (document.getElementById('tarteaucitron').offsetHeight < (windowInnerHeight / 2)) { + mainTop -= document.getElementById('tarteaucitronMainLineOffset').offsetHeight; + } + } + + // apply + tarteaucitron.userInterface.css('tarteaucitron', 'top', mainTop + 'px'); + } + + + } else if (type === 'cookie') { + + // put cookies list at bottom + if (document.getElementById('tarteaucitronAlertSmall') !== null) { + tarteaucitron.userInterface.css('tarteaucitronCookiesListContainer', 'bottom', (document.getElementById('tarteaucitronAlertSmall').offsetHeight) + 'px'); + } + + // height of cookies list + if (document.getElementById('tarteaucitronCookiesListContainer') !== null) { + + // reset + tarteaucitron.userInterface.css('tarteaucitronCookiesList', 'height', 'auto'); + + // calculate + cookiesListHeight = document.getElementById('tarteaucitronCookiesListContainer').offsetHeight; + cookiesCloseHeight = document.getElementById('tarteaucitronClosePanelCookie').offsetHeight; + cookiesTitleHeight = document.getElementById('tarteaucitronCookiesTitle').offsetHeight; + + // apply + tarteaucitron.userInterface.css('tarteaucitronCookiesList', 'height', (cookiesListHeight - cookiesCloseHeight - cookiesTitleHeight - 2) + 'px'); + } + } + } + }, + "cookie": { + "owner": {}, + "create": function (key, status) { + "use strict"; + var d = new Date(), + time = d.getTime(), + expireTime = time + 31536000000, // 365 days + regex = new RegExp("!" + key + "=(wait|true|false)", "g"), + cookie = tarteaucitron.cookie.read().replace(regex, ""), + value = 'tarteaucitron=' + cookie + '!' + key + '=' + status; + + if (tarteaucitron.cookie.read().indexOf(key + '=' + status) === -1) { + tarteaucitron.pro('!' + key + '=' + status); + } + + d.setTime(expireTime); + document.cookie = value + '; expires=' + d.toGMTString() + '; path=/;'; + }, + "read": function () { + "use strict"; + var nameEQ = "tarteaucitron=", + ca = document.cookie.split(';'), + i, + c; + + for (i = 0; i < ca.length; i += 1) { + c = ca[i]; + while (c.charAt(0) === ' ') { + c = c.substring(1, c.length); + } + if (c.indexOf(nameEQ) === 0) { + return c.substring(nameEQ.length, c.length); + } + } + return ''; + }, + "purge": function (arr) { + "use strict"; + var i; + + for (i = 0; i < arr.length; i += 1) { + document.cookie = arr[i] + '=; expires=Thu, 01 Jan 2000 00:00:00 GMT; path=/;'; + document.cookie = arr[i] + '=; expires=Thu, 01 Jan 2000 00:00:00 GMT; path=/; domain=.' + location.hostname + ';'; + document.cookie = arr[i] + '=; expires=Thu, 01 Jan 2000 00:00:00 GMT; path=/; domain=.' + location.hostname.split('.').slice(-2).join('.') + ';'; + } + }, + "checkCount": function (key) { + "use strict"; + var arr = tarteaucitron.services[key].cookies, + nb = arr.length, + nbCurrent = 0, + html = '', + i, + status = document.cookie.indexOf(key + '=true'); + + if (status >= 0 && nb === 0) { + html += tarteaucitron.lang.useNoCookie; + } else if (status >= 0) { + for (i = 0; i < nb; i += 1) { + if (document.cookie.indexOf(arr[i] + '=') !== -1) { + nbCurrent += 1; + if (tarteaucitron.cookie.owner[arr[i]] === undefined) { + tarteaucitron.cookie.owner[arr[i]] = []; + } + if (tarteaucitron.cookie.crossIndexOf(tarteaucitron.cookie.owner[arr[i]], tarteaucitron.services[key].name) === false) { + tarteaucitron.cookie.owner[arr[i]].push(tarteaucitron.services[key].name); + } + } + } + + if (nbCurrent > 0) { + html += tarteaucitron.lang.useCookieCurrent + ' ' + nbCurrent + ' cookie'; + if (nbCurrent > 1) { + html += 's'; + } + html += '.'; + } else { + html += tarteaucitron.lang.useNoCookie; + } + } else if (nb === 0) { + html = tarteaucitron.lang.noCookie; + } else { + html += tarteaucitron.lang.useCookie + ' ' + nb + ' cookie'; + if (nb > 1) { + html += 's'; + } + html += '.'; + } + + if (document.getElementById('tacCL' + key) !== null) { + document.getElementById('tacCL' + key).innerHTML = html; + } + }, + "crossIndexOf": function (arr, match) { + "use strict"; + var i; + for (i = 0; i < arr.length; i += 1) { + if (arr[i] === match) { + return true; + } + } + return false; + }, + "number": function () { + "use strict"; + var cookies = document.cookie.split(';'), + nb = (document.cookie !== '') ? cookies.length : 0, + html = '', + i, + name, + namea, + nameb, + c, + d, + s = (nb > 1) ? 's' : '', + savedname, + regex = /^https?\:\/\/([^\/?#]+)(?:[\/?#]|$)/i, + regexedDomain = (tarteaucitron.cdn.match(regex) !== null) ? tarteaucitron.cdn.match(regex)[1] : tarteaucitron.cdn, + host = (tarteaucitron.domain !== undefined) ? tarteaucitron.domain : regexedDomain; + + cookies = cookies.sort(function (a, b) { + namea = a.split('=', 1).toString().replace(/ /g, ''); + nameb = b.split('=', 1).toString().replace(/ /g, ''); + c = (tarteaucitron.cookie.owner[namea] !== undefined) ? tarteaucitron.cookie.owner[namea] : '0'; + d = (tarteaucitron.cookie.owner[nameb] !== undefined) ? tarteaucitron.cookie.owner[nameb] : '0'; + if (c + a > d + b) { return 1; } + if (c + a < d + b) { return -1; } + return 0; + }); + + if (document.cookie !== '') { + for (i = 0; i < nb; i += 1) { + name = cookies[i].split('=', 1).toString().replace(/ /g, ''); + if (tarteaucitron.cookie.owner[name] !== undefined && tarteaucitron.cookie.owner[name].join(' // ') !== savedname) { + savedname = tarteaucitron.cookie.owner[name].join(' // '); + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.cookie.owner[name].join(' // '); + html += '
'; + html += '
'; + } else if (tarteaucitron.cookie.owner[name] === undefined && host !== savedname) { + savedname = host; + html += '
'; + html += '
'; + html += ' ' + host; + html += '
'; + html += '
'; + } + html += '
'; + html += '
× ' + name + ''; + html += '
'; + html += '
' + cookies[i].split('=').slice(1).join('=') + '
'; + html += '
'; + } + } else { + html += '
'; + html += '
-
'; + html += '
'; + html += '
'; + } + + html += '
'; + + if (document.getElementById('tarteaucitronCookiesList') !== null) { + document.getElementById('tarteaucitronCookiesList').innerHTML = html; + } + + if (document.getElementById('tarteaucitronCookiesNumber') !== null) { + document.getElementById('tarteaucitronCookiesNumber').innerHTML = nb; + } + + if (document.getElementById('tarteaucitronCookiesNumberBis') !== null) { + document.getElementById('tarteaucitronCookiesNumberBis').innerHTML = nb + ' cookie' + s; + } + + for (i = 0; i < tarteaucitron.job.length; i += 1) { + tarteaucitron.cookie.checkCount(tarteaucitron.job[i]); + } + } + }, + "getLanguage": function () { + "use strict"; + if (!navigator) { return 'en'; } + + var availableLanguages = 'cs,en,fr,es,it,de,pt,pl,ru', + defaultLanguage = 'en', + lang = navigator.language || navigator.browserLanguage || + navigator.systemLanguage || navigator.userLang || null, + userLanguage = lang.substr(0, 2); + + if (tarteaucitronForceLanguage !== '') { + if (availableLanguages.indexOf(tarteaucitronForceLanguage) !== -1) { + return tarteaucitronForceLanguage; + } + } + + if (availableLanguages.indexOf(userLanguage) === -1) { + return defaultLanguage; + } + return userLanguage; + }, + "getLocale": function () { + "use strict"; + if (!navigator) { return 'en_US'; } + + var lang = navigator.language || navigator.browserLanguage || + navigator.systemLanguage || navigator.userLang || null, + userLanguage = lang.substr(0, 2); + + if (userLanguage === 'fr') { + return 'fr_FR'; + } else if (userLanguage === 'en') { + return 'en_US'; + } else if (userLanguage === 'de') { + return 'de_DE'; + } else if (userLanguage === 'es') { + return 'es_ES'; + } else if (userLanguage === 'it') { + return 'it_IT'; + } else if (userLanguage === 'pt') { + return 'pt_PT'; + } else { + return 'en_US'; + } + }, + "addScript": function (url, id, callback, execute, attrName, attrVal) { + "use strict"; + var script, + done = false; + + if (execute === false) { + if (typeof callback === 'function') { + callback(); + } + } else { + script = document.createElement('script'); + script.type = 'text/javascript'; + script.id = (id !== undefined) ? id : ''; + script.async = true; + script.src = url; + + if (attrName !== undefined && attrVal !== undefined) { + script.setAttribute(attrName, attrVal); + } + + if (typeof callback === 'function') { + script.onreadystatechange = script.onload = function () { + var state = script.readyState; + if (!done && (!state || /loaded|complete/.test(state))) { + done = true; + callback(); + } + }; + } + + document.getElementsByTagName('head')[0].appendChild(script); + } + }, + "makeAsync": { + "antiGhost": 0, + "buffer": '', + "init": function (url, id) { + "use strict"; + var savedWrite = document.write, + savedWriteln = document.writeln; + + document.write = function (content) { + tarteaucitron.makeAsync.buffer += content; + }; + document.writeln = function (content) { + tarteaucitron.makeAsync.buffer += content.concat("\n"); + }; + + setTimeout(function () { + document.write = savedWrite; + document.writeln = savedWriteln; + }, 20000); + + tarteaucitron.makeAsync.getAndParse(url, id); + }, + "getAndParse": function (url, id) { + "use strict"; + if (tarteaucitron.makeAsync.antiGhost > 9) { + tarteaucitron.makeAsync.antiGhost = 0; + return; + } + tarteaucitron.makeAsync.antiGhost += 1; + tarteaucitron.addScript(url, '', function () { + if (document.getElementById(id) !== null) { + document.getElementById(id).innerHTML += " " + tarteaucitron.makeAsync.buffer; + tarteaucitron.makeAsync.buffer = ''; + tarteaucitron.makeAsync.execJS(id); + } + }); + }, + "execJS": function (id) { + /* not strict because third party scripts may have errors */ + var i, + scripts, + childId, + type; + + if (document.getElementById(id) === null) { + return; + } + + scripts = document.getElementById(id).getElementsByTagName('script'); + for (i = 0; i < scripts.length; i += 1) { + type = (scripts[i].getAttribute('type') !== null) ? scripts[i].getAttribute('type') : ''; + if (type === '') { + type = (scripts[i].getAttribute('language') !== null) ? scripts[i].getAttribute('language') : ''; + } + if (scripts[i].getAttribute('src') !== null && scripts[i].getAttribute('src') !== '') { + childId = id + Math.floor(Math.random() * 99999999999); + document.getElementById(id).innerHTML += '
'; + tarteaucitron.makeAsync.getAndParse(scripts[i].getAttribute('src'), childId); + } else if (type.indexOf('javascript') !== -1 || type === '') { + eval(scripts[i].innerHTML); + } + } + } + }, + "fallback": function (matchClass, content, noInner) { + "use strict"; + var elems = document.getElementsByTagName('*'), + i, + index = 0; + + for (i in elems) { + if (elems[i] !== undefined) { + for (index = 0; index < matchClass.length; index += 1) { + if ((' ' + elems[i].className + ' ') + .indexOf(' ' + matchClass[index] + ' ') > -1) { + if (typeof content === 'function') { + if (noInner === true) { + content(elems[i]); + } else { + elems[i].innerHTML = content(elems[i]); + } + } else { + elems[i].innerHTML = content; + } + } + } + } + } + }, + "engage": function (id) { + "use strict"; + var html = '', + r = Math.floor(Math.random() * 100000); + + html += '
'; + html += '
'; + html += ' ' + tarteaucitron.services[id].name + ' ' + tarteaucitron.lang.fallback; + html += '
'; + html += ' ✓ ' + tarteaucitron.lang.allow; + html += '
'; + html += '
'; + html += '
'; + + return html; + }, + "extend": function (a, b) { + "use strict"; + var prop; + for (prop in b) { + if (b.hasOwnProperty(prop)) { + a[prop] = b[prop]; + } + } + }, + "proTemp": '', + "proTimer": function () { + "use strict"; + setTimeout(tarteaucitron.proPing, 1000); + }, + "pro": function (list) { + "use strict"; + tarteaucitron.proTemp += list; + clearTimeout(tarteaucitron.proTimer); + tarteaucitron.proTimer = setTimeout(tarteaucitron.proPing, 2500); + }, + "proPing": function () { + "use strict"; + if (tarteaucitron.uuid !== '' && tarteaucitron.uuid !== undefined && tarteaucitron.proTemp !== '') { + var div = document.getElementById('tarteaucitronPremium'), + timestamp = new Date().getTime(), + url = '//opt-out.ferank.eu/premium.php?'; + + if (div === null) { + return; + } + + url += 'domain=' + tarteaucitron.domain + '&'; + url += 'uuid=' + tarteaucitron.uuid + '&'; + url += 'c=' + encodeURIComponent(tarteaucitron.proTemp) + '&'; + url += '_' + timestamp; + + div.innerHTML = ''; + + tarteaucitron.proTemp = ''; + } + + tarteaucitron.cookie.number(); + } +}; \ No newline at end of file diff --git a/docs/pages/assets/js/tarteaucitron/tarteaucitron.services.js b/docs/pages/assets/js/tarteaucitron/tarteaucitron.services.js new file mode 100644 index 0000000..8133f81 --- /dev/null +++ b/docs/pages/assets/js/tarteaucitron/tarteaucitron.services.js @@ -0,0 +1,1849 @@ +/*global tarteaucitron, ga, Shareaholic, stLight, clicky, top, google, Typekit, FB, ferankReady, IN, stButtons, twttr, PCWidget*/ +/*jslint regexp: true, nomen: true*/ + +// generic iframe +tarteaucitron.services.iframe = { + "key": "iframe", + "type": "other", + "name": "Web content", + "uri": "", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['tac_iframe'], function (x) { + var width = x.getAttribute("width"), + height = x.getAttribute("height"), + url = x.getAttribute("data-url"); + + return ''; + }); + }, + "fallback": function () { + "use strict"; + var id = 'iframe'; + tarteaucitron.fallback(['tac_iframe'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// addthis +tarteaucitron.services.addthis = { + "key": "addthis", + "type": "social", + "name": "AddThis", + "uri": "https://www.addthis.com/privacy/privacy-policy#publisher-visitors", + "needConsent": true, + "cookies": ['__atuvc', '__atuvs'], + "js": function () { + "use strict"; + if (tarteaucitron.user.addthisPubId === undefined) { + return; + } + if (tarteaucitron.isAjax === true) { + window.addthis = null; + window._adr = null; + window._atc = null; + window._atd = null; + window._ate = null; + window._atr = null; + window._atw = null; + } + tarteaucitron.fallback(['addthis_sharing_toolbox'], ''); + tarteaucitron.addScript('//s7.addthis.com/js/300/addthis_widget.js#pubid=' + tarteaucitron.user.addthisPubId); + }, + "fallback": function () { + "use strict"; + var id = 'addthis'; + tarteaucitron.fallback(['addthis_sharing_toolbox'], tarteaucitron.engage(id)); + } +}; + +// addtoanyfeed +tarteaucitron.services.addtoanyfeed = { + "key": "addtoanyfeed", + "type": "social", + "name": "AddToAny (feed)", + "uri": "https://www.addtoany.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.addtoanyfeedUri === undefined) { + return; + } + tarteaucitron.user.addtoanyfeedSubscribeLink = 'https://www.addtoany.com/subscribe?linkurl=' + tarteaucitron.user.addtoanyfeedUri; + window.a2a_config = window.a2a_config || {}; + window.a2a_config.linkurl = tarteaucitron.user.addtoanyfeedUri; + tarteaucitron.addScript('//static.addtoany.com/menu/feed.js'); + }, + "fallback": function () { + "use strict"; + tarteaucitron.user.addtoanyfeedSubscribeLink = 'https://www.addtoany.com/subscribe?linkurl=' + tarteaucitron.user.addtoanyfeedUri; + } +}; + +// addtoanyshare +tarteaucitron.services.addtoanyshare = { + "key": "addtoanyshare", + "type": "social", + "name": "AddToAny (share)", + "uri": "https://www.addtoany.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['tac_addtoanyshare'], ''); + tarteaucitron.addScript('//static.addtoany.com/menu/page.js'); + }, + "fallback": function () { + "use strict"; + var id = 'addtoanyshare'; + tarteaucitron.fallback(['tac_addtoanyshare'], tarteaucitron.engage(id)); + } +}; + +// alexa +tarteaucitron.services.alexa = { + "key": "alexa", + "type": "analytic", + "name": "Alexa", + "uri": "https://www.alexa.com/help/privacy", + "needConsent": true, + "cookies": ['__asc', '__auc'], + "js": function () { + "use strict"; + if (tarteaucitron.user.alexaAccountID === undefined) { + return; + } + window._atrk_opts = { + atrk_acct: tarteaucitron.user.alexaAccountID, + domain: window.location.hostname.match(/[^\.]*\.[^.]*$/)[0], + dynamic: true + }; + tarteaucitron.addScript('https://d31qbv1cthcecs.cloudfront.net/atrk.js'); + } +}; + +// amazon +tarteaucitron.services.amazon = { + "key": "amazon", + "type": "ads", + "name": "Amazon", + "uri": "https://www.amazon.fr/gp/help/customer/display.html?ie=UTF8&*Version*=1&*entries*=0&nodeId=201149360", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['amazon_product'], function (x) { + var amazonId = x.getAttribute("amazonid"), + productId = x.getAttribute("productid"), + url = '//ws-eu.amazon-adsystem.com/widgets/q?ServiceVersion=20070822&OneJS=1&Operation=GetAdHtml&MarketPlace=' + tarteaucitron.getLanguage().toUpperCase() + '&source=ss&ref=ss_til&ad_type=product_link&tracking_id=' + amazonId + '&marketplace=amazon®ion=' + tarteaucitron.getLanguage().toUpperCase() + '&placement=' + productId + '&asins=' + productId + '&show_border=true&link_opens_in_new_window=true', + iframe = ''; + + return iframe; + }); + }, + "fallback": function () { + "use strict"; + var id = 'amazon'; + tarteaucitron.fallback(['amazon_product'], tarteaucitron.engage(id)); + } +}; + +// calameo +tarteaucitron.services.calameo = { + "key": "calameo", + "type": "video", + "name": "Calameo", + "uri": "https://fr.calameo.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['calameo-canvas'], function (x) { + var id = x.getAttribute("data-id"), + width = x.getAttribute("width"), + height = x.getAttribute("height"), + url = '//v.calameo.com/?bkcode=' + id; + + return ''; + }); + }, + "fallback": function () { + "use strict"; + var id = 'calameo'; + tarteaucitron.fallback(['calameo-canvas'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// clicky +tarteaucitron.services.clicky = { + "key": "clicky", + "type": "analytic", + "name": "Clicky", + "uri": "https://clicky.com/terms", + "needConsent": true, + "cookies": ['_jsuid', '_eventqueue', '_referrer_og', '_utm_og', '_first_pageview', 'clicky_olark', 'no_trackyy_' + tarteaucitron.user.clickyId, 'unpoco_' + tarteaucitron.user.clickyId, 'heatmaps_g2g_' + tarteaucitron.user.clickyId], + "js": function () { + "use strict"; + if (tarteaucitron.user.clickyId === undefined) { + return; + } + tarteaucitron.addScript('//static.getclicky.com/js', '', function () { + if (typeof clicky.init === 'function') { + clicky.init(tarteaucitron.user.clickyId); + } + if (typeof tarteaucitron.user.clickyMore === 'function') { + tarteaucitron.user.clickyMore(); + } + }); + } +}; + +// clicmanager +tarteaucitron.services.clicmanager = { + "key": "clicmanager", + "type": "ads", + "name": "Clicmanager", + "uri": "http://www.clicmanager.fr/infos_legales.php", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + var uniqIds = [], + i, + uri; + + tarteaucitron.fallback(['clicmanager-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + uri = '//ads.clicmanager.fr/exe.php?'; + uri += 'c=' + document.getElementById(uniqIds[i]).getAttribute('c') + '&'; + uri += 's=' + document.getElementById(uniqIds[i]).getAttribute('s') + '&'; + uri += 't=' + document.getElementById(uniqIds[i]).getAttribute('t'); + + tarteaucitron.makeAsync.init(uri, uniqIds[i]); + } + }, + "fallback": function () { + "use strict"; + var id = 'clicmanager'; + tarteaucitron.fallback(['clicmanager-canvas'], tarteaucitron.engage(id)); + } +}; + +// crazyegg +tarteaucitron.services.crazyegg = { + "key": "crazyegg", + "type": "analytic", + "name": "Crazy Egg", + "uri": "https://www.crazyegg.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + + if (tarteaucitron.user.crazyeggId === undefined) { + return; + } + + tarteaucitron.addScript('//script.crazyegg.com/pages/scripts/' + tarteaucitron.user.crazyeggId.substr(0, 4) + '/' + tarteaucitron.user.crazyeggId.substr(4, 4) + '.js'); + } +}; + +// criteo +tarteaucitron.services.criteo = { + "key": "criteo", + "type": "ads", + "name": "Criteo", + "uri": "http://www.criteo.com/privacy/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + document.MAX_ct0 = ''; + var uniqIds = [], + i, + uri; + + tarteaucitron.fallback(['criteo-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + uri = '//cas.criteo.com/delivery/ajs.php?'; + uri += 'zoneid=' + document.getElementById(uniqIds[i]).getAttribute('zoneid'); + uri += '&nodis=1&cb=' + Math.floor(Math.random() * 99999999999); + uri += '&loc=' + encodeURI(window.location); + uri += (document.MAX_used !== ',') ? '&exclude=' + document.MAX_used : ''; + uri += (document.charset !== undefined ? '&charset=' + document.charset : ''); + uri += (document.characterSet !== undefined ? '&charset=' + document.characterSet : ''); + uri += (document.referrer !== undefined) ? '&referer=' + encodeURI(document.referrer) : ''; + uri += (document.context !== undefined) ? '&context=' + encodeURI(document.context) : ''; + uri += ((document.MAX_ct0 !== undefined) && (document.MAX_ct0.substring(0, 4) === 'http')) ? '&ct0=' + encodeURI(document.MAX_ct0) : ''; + uri += (document.mmm_fo !== undefined) ? '&mmm_fo=1' : ''; + + tarteaucitron.makeAsync.init(uri, uniqIds[i]); + } + }, + "fallback": function () { + "use strict"; + var id = 'criteo'; + tarteaucitron.fallback(['criteo-canvas'], tarteaucitron.engage(id)); + } +}; + +// dailymotion +tarteaucitron.services.dailymotion = { + "key": "dailymotion", + "type": "video", + "name": "Dailymotion", + "uri": "https://www.dailymotion.com/legal/privacy", + "needConsent": true, + "cookies": ['ts', 'dmvk', 'hist', 'v1st', 's_vi'], + "js": function () { + "use strict"; + tarteaucitron.fallback(['dailymotion_player'], function (x) { + var video_id = x.getAttribute("videoID"), + video_width = x.getAttribute("width"), + frame_width = 'width=', + video_height = x.getAttribute("height"), + frame_height = 'height=', + video_frame, + params = 'info=' + x.getAttribute("showinfo") + '&autoPlay=' + x.getAttribute("autoplay"); + + if (video_id === undefined) { + return ""; + } + if (video_width !== undefined) { + frame_width += '"' + video_width + '" '; + } else { + frame_width += '"" '; + } + if (video_height !== undefined) { + frame_height += '"' + video_height + '" '; + } else { + frame_height += '"" '; + } + video_frame = ''; + return video_frame; + }); + }, + "fallback": function () { + "use strict"; + var id = 'dailymotion'; + tarteaucitron.fallback(['dailymotion_player'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// dating affiliation +tarteaucitron.services.datingaffiliation = { + "key": "datingaffiliation", + "type": "ads", + "name": "Dating Affiliation", + "uri": "http://www.dating-affiliation.com/conditions-generales.php", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['datingaffiliation-canvas'], function (x) { + var comfrom = x.getAttribute("data-comfrom"), + r = x.getAttribute("data-r"), + p = x.getAttribute("data-p"), + cf0 = x.getAttribute("data-cf0"), + langue = x.getAttribute("data-langue"), + forward_affiliate = x.getAttribute("data-forwardAffiliate"), + cf2 = x.getAttribute("data-cf2"), + cfsa2 = x.getAttribute("data-cfsa2"), + width = x.getAttribute("width"), + height = x.getAttribute("height"), + url = 'http://www.tools-affil2.com/rotaban/ban.php?' + comfrom; + + return ''; + }); + }, + "fallback": function () { + "use strict"; + var id = 'datingaffiliation'; + tarteaucitron.fallback(['datingaffiliation-canvas'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// dating affiliation popup +tarteaucitron.services.datingaffiliationpopup = { + "key": "datingaffiliationpopup", + "type": "ads", + "name": "Dating Affiliation (Pop Up)", + "uri": "http://www.dating-affiliation.com/conditions-generales.php", + "needConsent": true, + "cookies": ['__utma', '__utmb', '__utmc', '__utmt_Tools', '__utmv', '__utmz', '_ga', '_gat', '_gat_UA-65072040-17', '__da-pu-xflirt-ID-pc-o169'], + "js": function () { + "use strict"; + var uniqIds = [], + i, + uri; + + tarteaucitron.fallback(['datingaffiliationpopup-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + uri = 'http://www.promotools.biz/da/popunder/script.php?'; + uri += 'comfrom=' + document.getElementById(uniqIds[i]).getAttribute('comfrom') + '&'; + uri += 'promo=' + document.getElementById(uniqIds[i]).getAttribute('promo') + '&'; + uri += 'product_id=' + document.getElementById(uniqIds[i]).getAttribute('productid') + '&'; + uri += 'submitconfig=' + document.getElementById(uniqIds[i]).getAttribute('submitconfig') + '&'; + uri += 'ur=' + document.getElementById(uniqIds[i]).getAttribute('ur') + '&'; + uri += 'brand=' + document.getElementById(uniqIds[i]).getAttribute('brand') + '&'; + uri += 'lang=' + document.getElementById(uniqIds[i]).getAttribute('lang') + '&'; + uri += 'cf0=' + document.getElementById(uniqIds[i]).getAttribute('cf0') + '&'; + uri += 'cf2=' + document.getElementById(uniqIds[i]).getAttribute('cf2') + '&'; + uri += 'subid1=' + document.getElementById(uniqIds[i]).getAttribute('subid1') + '&'; + uri += 'cfsa2=' + document.getElementById(uniqIds[i]).getAttribute('cfsa2') + '&'; + uri += 'subid2=' + document.getElementById(uniqIds[i]).getAttribute('subid2') + '&'; + uri += 'nicheId=' + document.getElementById(uniqIds[i]).getAttribute('nicheid') + '&'; + uri += 'degreId=' + document.getElementById(uniqIds[i]).getAttribute('degreid') + '&'; + uri += 'bt=' + document.getElementById(uniqIds[i]).getAttribute('bt') + '&'; + uri += 'vis=' + document.getElementById(uniqIds[i]).getAttribute('vis') + '&'; + uri += 'hid=' + document.getElementById(uniqIds[i]).getAttribute('hid') + '&'; + uri += 'snd=' + document.getElementById(uniqIds[i]).getAttribute('snd') + '&'; + uri += 'aabd=' + document.getElementById(uniqIds[i]).getAttribute('aabd') + '&'; + uri += 'aabs=' + document.getElementById(uniqIds[i]).getAttribute('aabs'); + + tarteaucitron.makeAsync.init(uri, uniqIds[i]); + } + }, + "fallback": function () { + "use strict"; + var id = 'datingaffiliationpopup'; + tarteaucitron.fallback(['datingaffiliationpopup-canvas'], tarteaucitron.engage(id)); + } +}; + +// disqus +tarteaucitron.services.disqus = { + "key": "disqus", + "type": "comment", + "name": "Disqus", + "uri": "https://help.disqus.com/customer/portal/articles/466259-privacy-policy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.disqusShortname === undefined) { + return; + } + tarteaucitron.addScript('//' + tarteaucitron.user.disqusShortname + '.disqus.com/embed.js'); + tarteaucitron.addScript('//' + tarteaucitron.user.disqusShortname + '.disqus.com/count.js'); + }, + "fallback": function () { + "use strict"; + var id = 'disqus'; + + if (document.getElementById('disqus_thread')) { + document.getElementById('disqus_thread').innerHTML = tarteaucitron.engage(id); + } + } +}; + +// ekomi +tarteaucitron.services.ekomi = { + "key": "ekomi", + "type": "social", + "name": "eKomi", + "uri": "http://www.ekomi-us.com/us/privacy/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.ekomiCertId === undefined) { + return; + } + window.eKomiIntegrationConfig = [ + {certId: tarteaucitron.user.ekomiCertId} + ]; + tarteaucitron.addScript('//connect.ekomi.de/integration_1410173009/' + tarteaucitron.user.ekomiCertId + '.js'); + } +}; + +// etracker +tarteaucitron.services.etracker = { + "key": "etracker", + "type": "analytic", + "name": "eTracker", + "uri": "https://www.etracker.com/en/data-protection.html", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.etracker === undefined) { + return; + } + + tarteaucitron.addScript('//static.etracker.com/code/e.js', '_etLoader', function () {}, true, "data-secure-code", tarteaucitron.user.etracker); + } +}; + +// facebook +tarteaucitron.services.facebook = { + "key": "facebook", + "type": "social", + "name": "Facebook", + "uri": "https://www.facebook.com/policies/cookies/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['fb-post', 'fb-follow', 'fb-activity', 'fb-send', 'fb-share-button', 'fb-like'], ''); + tarteaucitron.addScript('//connect.facebook.net/' + tarteaucitron.getLocale() + '/sdk.js#xfbml=1&version=v2.0', 'facebook-jssdk'); + if (tarteaucitron.isAjax === true) { + if (typeof FB !== "undefined") { + FB.XFBML.parse(); + } + } + }, + "fallback": function () { + "use strict"; + var id = 'facebook'; + tarteaucitron.fallback(['fb-post', 'fb-follow', 'fb-activity', 'fb-send', 'fb-share-button', 'fb-like'], tarteaucitron.engage(id)); + } +}; + +// facebooklikebox +tarteaucitron.services.facebooklikebox = { + "key": "facebooklikebox", + "type": "social", + "name": "Facebook (like box)", + "uri": "https://www.facebook.com/policies/cookies/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['fb-like-box', 'fb-page'], ''); + tarteaucitron.addScript('//connect.facebook.net/' + tarteaucitron.getLocale() + '/sdk.js#xfbml=1&version=v2.3', 'facebook-jssdk'); + if (tarteaucitron.isAjax === true) { + if (typeof FB !== "undefined") { + FB.XFBML.parse(); + } + } + }, + "fallback": function () { + "use strict"; + var id = 'facebooklikebox'; + tarteaucitron.fallback(['fb-like-box', 'fb-page'], tarteaucitron.engage(id)); + } +}; + +// facebookcomment +tarteaucitron.services.facebookcomment = { + "key": "facebookcomment", + "type": "comment", + "name": "Facebook (commentaire)", + "uri": "https://www.facebook.com/policies/cookies/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['fb-comments'], ''); + tarteaucitron.addScript('//connect.facebook.net/' + tarteaucitron.getLocale() + '/sdk.js#xfbml=1&version=v2.0', 'facebook-jssdk'); + if (tarteaucitron.isAjax === true) { + if (typeof FB !== "undefined") { + FB.XFBML.parse(); + } + } + }, + "fallback": function () { + "use strict"; + var id = 'facebookcomment'; + tarteaucitron.fallback(['fb-comments'], tarteaucitron.engage(id)); + } +}; + +// ferank +tarteaucitron.services.ferank = { + "key": "ferank", + "type": "analytic", + "name": "FERank", + "uri": "https://www.ferank.fr/respect-vie-privee/#mesureaudience", + "needConsent": false, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('//static.ferank.fr/pixel.js', '', function () { + if (typeof tarteaucitron.user.ferankMore === 'function') { + tarteaucitron.user.ferankMore(); + } + }); + } +}; + +// ferank pub +tarteaucitron.services.ferankpub = { + "key": "ferankpub", + "type": "ads", + "name": "FERank (pub)", + "uri": "https://www.ferank.fr/respect-vie-privee/#regiepublicitaire", + "needConsent": false, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('//static.ferank.fr/publicite.async.js'); + if (tarteaucitron.isAjax === true) { + if (typeof ferankReady === 'function') { + ferankReady(); + } + } + }, + "fallback": function () { + "use strict"; + var id = 'ferankpub'; + tarteaucitron.fallback(['ferank-publicite'], tarteaucitron.engage(id)); + } +}; + +// get+ +tarteaucitron.services.getplus = { + "key": "getplus", + "type": "analytic", + "name": "Get+", + "uri": "http://www.getplus.fr/Conditions-generales-de-vente_a226.html", + "needConsent": true, + "cookies": ['_first_pageview', '_jsuid', 'no_trackyy_' + tarteaucitron.user.getplusId, '_eventqueue'], + "js": function () { + "use strict"; + if (tarteaucitron.user.getplusId === undefined) { + return; + } + + window.webleads_site_ids = window.webleads_site_ids || []; + window.webleads_site_ids.push(tarteaucitron.user.getplusId); + tarteaucitron.addScript('//stats.webleads-tracker.com/js'); + } +}; + +// google+ +tarteaucitron.services.gplus = { + "key": "gplus", + "type": "social", + "name": "Google+", + "uri": "https://policies.google.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('https://apis.google.com/js/platform.js'); + }, + "fallback": function () { + "use strict"; + var id = 'gplus'; + tarteaucitron.fallback(['g-plus', 'g-plusone'], tarteaucitron.engage(id)); + } +}; + +// google+ badge +tarteaucitron.services.gplusbadge = { + "key": "gplusbadge", + "type": "social", + "name": "Google+ (badge)", + "uri": "https://policies.google.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('https://apis.google.com/js/platform.js'); + }, + "fallback": function () { + "use strict"; + var id = 'gplusbadge'; + tarteaucitron.fallback(['g-page', 'g-person'], tarteaucitron.engage(id)); + } +}; + +// google adsense +tarteaucitron.services.adsense = { + "key": "adsense", + "type": "ads", + "name": "Google Adsense", + "uri": "http://www.google.com/ads/preferences/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js'); + }, + "fallback": function () { + "use strict"; + var id = 'adsense'; + tarteaucitron.fallback(['adsbygoogle'], tarteaucitron.engage(id)); + } +}; + +// google partners badge +tarteaucitron.services.googlepartners = { + "key": "googlepartners", + "type": "ads", + "name": "Google Partners Badge", + "uri": "http://www.google.com/ads/preferences/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('https://apis.google.com/js/platform.js'); + }, + "fallback": function () { + "use strict"; + var id = 'googlepartners'; + tarteaucitron.fallback(['g-partnersbadge'], tarteaucitron.engage(id)); + } +}; + +// google adsense search (form) +tarteaucitron.services.adsensesearchform = { + "key": "adsensesearchform", + "type": "ads", + "name": "Google Adsense Search (form)", + "uri": "http://www.google.com/ads/preferences/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('//www.google.com/coop/cse/brand?form=cse-search-box&lang=' + tarteaucitron.getLanguage()); + } +}; + +// google adsense search (result) +tarteaucitron.services.adsensesearchresult = { + "key": "adsensesearchresult", + "type": "ads", + "name": "Google Adsense Search (result)", + "uri": "http://www.google.com/ads/preferences/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.adsensesearchresultCx === undefined) { + return; + } + tarteaucitron.addScript('//www.google.com/cse/cse.js?cx=' + tarteaucitron.user.adsensesearchresultCx); + }, + "fallback": function () { + "use strict"; + var id = 'adsensesearchresult'; + + if (document.getElementById('gcse_searchresults')) { + document.getElementById('gcse_searchresults').innerHTML = tarteaucitron.engage(id); + } + } +}; + +// googleadwordsconversion +tarteaucitron.services.googleadwordsconversion = { + "key": "googleadwordsconversion", + "type": "ads", + "name": "Google Adwords (conversion)", + "uri": "https://www.google.com/settings/ads", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.adwordsconversionId === undefined) { + return; + } + + tarteaucitron.addScript('//www.googleadservices.com/pagead/conversion_async.js', '', function () { + window.google_trackConversion({ + google_conversion_id: tarteaucitron.user.adwordsconversionId, + google_conversion_label: tarteaucitron.user.adwordsconversionLabel, + google_conversion_language: tarteaucitron.user.adwordsconversionLanguage, + google_conversion_format: tarteaucitron.user.adwordsconversionFormat, + google_conversion_color: tarteaucitron.user.adwordsconversionColor, + google_conversion_value: tarteaucitron.user.adwordsconversionValue, + google_conversion_currency: tarteaucitron.user.adwordsconversionCurrency, + google_custom_params: { + parameter1: tarteaucitron.user.adwordsconversionCustom1, + parameter2: tarteaucitron.user.adwordsconversionCustom2 + } + }); + }); + } +}; + +// googleadwordsremarketing +tarteaucitron.services.googleadwordsremarketing = { + "key": "googleadwordsremarketing", + "type": "ads", + "name": "Google Adwords (remarketing)", + "uri": "https://www.google.com/settings/ads", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.adwordsremarketingId === undefined) { + return; + } + + tarteaucitron.addScript('//www.googleadservices.com/pagead/conversion_async.js', '', function () { + window.google_trackConversion({ + google_conversion_id: tarteaucitron.user.adwordsremarketingId, + google_remarketing_only: true + }); + }); + } +}; + +// google analytics (old) +tarteaucitron.services.gajs = { + "key": "gajs", + "type": "analytic", + "name": "Google Analytics (ga.js)", + "uri": "https://support.google.com/analytics/answer/6004245", + "needConsent": true, + "cookies": ['_ga', '_gat', '__utma', '__utmb', '__utmc', '__utmt', '__utmz'], + "js": function () { + "use strict"; + window._gaq = window._gaq || []; + window._gaq.push(['_setAccount', tarteaucitron.user.gajsUa]); + window._gaq.push(['_trackPageview']); + + tarteaucitron.addScript('//www.google-analytics.com/ga.js', '', function () { + if (typeof tarteaucitron.user.gajsMore === 'function') { + tarteaucitron.user.gajsMore(); + } + }); + } +}; + +// google analytics +tarteaucitron.services.analytics = { + "key": "analytics", + "type": "analytic", + "name": "Google Analytics (universal)", + "uri": "https://support.google.com/analytics/answer/6004245", + "needConsent": true, + "cookies": ['_ga', '_gat', '_gid', '__utma', '__utmb', '__utmc', '__utmt', '__utmz'], + "js": function () { + "use strict"; + window.GoogleAnalyticsObject = 'ga'; + window.ga = window.ga || function () { + window.ga.q = window.ga.q || []; + window.ga.q.push(arguments); + }; + window.ga.l = new Date(); + + tarteaucitron.addScript('//www.google-analytics.com/analytics.js', '', function () { + ga('create', tarteaucitron.user.analyticsUa, {'cookieExpires': 34128000}); + ga('send', 'pageview'); + if (typeof tarteaucitron.user.analyticsMore === 'function') { + tarteaucitron.user.analyticsMore(); + } + }); + } +}; + +// google analytics +tarteaucitron.services.gtag = { + "key": "gtag", + "type": "analytic", + "name": "Google Analytics (gtag.js)", + "uri": "https://support.google.com/analytics/answer/6004245", + "needConsent": true, + "cookies": ['_ga', '_gat', '_gid', '__utma', '__utmb', '__utmc', '__utmt', '__utmz'], + "js": function () { + "use strict"; + window.dataLayer = window.dataLayer || []; + + tarteaucitron.addScript('//www.googletagmanager.com/gtag/js?id=' + tarteaucitron.user.gtagUa, '', function () { + function gtag(){dataLayer.push(arguments);} + gtag('js', new Date()); + gtag('config', tarteaucitron.user.gtagUa); + + if (typeof tarteaucitron.user.gtagMore === 'function') { + tarteaucitron.user.gtagMore(); + } + }); + } +}; + +// google maps +tarteaucitron.services.googlemaps = { + "key": "googlemaps", + "type": "api", + "name": "Google Maps", + "uri": "http://www.google.com/ads/preferences/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + var mapOptions, + map, + uniqIds = [], + i; + + if (tarteaucitron.user.mapscallback === undefined) { + tarteaucitron.user.mapscallback = 'tac_googlemaps_callback'; + } + + tarteaucitron.addScript('//maps.googleapis.com/maps/api/js?v=3.exp&key=' + tarteaucitron.user.googlemapsKey + '&callback='+tarteaucitron.user.mapscallback); + + window.tac_googlemaps_callback = function () { + tarteaucitron.fallback(['googlemaps-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + mapOptions = { + zoom: parseInt(document.getElementById(uniqIds[i]).getAttribute('zoom'), 10), + center: new google.maps.LatLng(parseFloat(document.getElementById(uniqIds[i]).getAttribute('latitude'), 10), parseFloat(document.getElementById(uniqIds[i]).getAttribute('longitude'), 10)) + }; + map = new google.maps.Map(document.getElementById(uniqIds[i]), mapOptions); + } + }; + }, + "fallback": function () { + "use strict"; + var id = 'googlemaps'; + tarteaucitron.fallback(['googlemaps-canvas'], tarteaucitron.engage(id)); + } +}; + +// google tag manager +tarteaucitron.services.googletagmanager = { + "key": "googletagmanager", + "type": "api", + "name": "Google Tag Manager", + "uri": "http://www.google.com/ads/preferences/", + "needConsent": true, + "cookies": ['_ga', '_gat', '__utma', '__utmb', '__utmc', '__utmt', '__utmz', '__gads', '_drt_', 'FLC', 'exchange_uid', 'id', 'fc', 'rrs', 'rds', 'rv', 'uid', 'UIDR', 'UID', 'clid', 'ipinfo', 'acs'], + "js": function () { + "use strict"; + if (tarteaucitron.user.googletagmanagerId === undefined) { + return; + } + window.dataLayer = window.dataLayer || []; + window.dataLayer.push({ + 'gtm.start': new Date().getTime(), + event: 'gtm.js' + }); + tarteaucitron.addScript('//www.googletagmanager.com/gtm.js?id=' + tarteaucitron.user.googletagmanagerId); + } +}; + +// jsapi +tarteaucitron.services.jsapi = { + "key": "jsapi", + "type": "api", + "name": "Google jsapi", + "uri": "http://www.google.com/policies/privacy/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.addScript('//www.google.com/jsapi'); + } +}; + +// linkedin +tarteaucitron.services.linkedin = { + "key": "linkedin", + "type": "social", + "name": "Linkedin", + "uri": "https://www.linkedin.com/legal/cookie_policy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['tacLinkedin'], ''); + tarteaucitron.addScript('//platform.linkedin.com/in.js'); + if (tarteaucitron.isAjax === true) { + if (typeof IN !== "undefined") { + IN.parse(); + } + } + }, + "fallback": function () { + "use strict"; + var id = 'linkedin'; + tarteaucitron.fallback(['tacLinkedin'], tarteaucitron.engage(id)); + } +}; + +// mautic +tarteaucitron.services.mautic = { + "key": "mautic", + "type": "analytic", + "name": "Mautic", + "uri": "https://www.mautic.org/privacy-policy/", + "needConsent": true, + "cookies": ['mtc_id', 'mtc_sid'], + "js": function () { + "use strict"; + if (tarteaucitron.user.mauticurl === undefined) { + return; + } + + window['MauticTrackingObject'] = 'mt'; + window['mt'] = window['mt'] || function() { + (window['mt'].q = window['mt'].q || []).push(arguments); + }; + + tarteaucitron.addScript(tarteaucitron.user.mauticurl, '', function() { + mt('send', 'pageview'); + }); + } +}; + +// microsoftcampaignanalytics +tarteaucitron.services.microsoftcampaignanalytics = { + "key": "microsoftcampaignanalytics", + "type": "analytic", + "name": "Microsoft Campaign Analytics", + "uri": "https://privacy.microsoft.com/privacystatement/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.microsoftcampaignanalyticsUUID === undefined) { + return; + } + + tarteaucitron.addScript('//flex.atdmt.com/mstag/site/' + tarteaucitron.user.microsoftcampaignanalyticsUUID + '/mstag.js', 'mstag_tops', function () { + window.mstag = {loadTag : function () {}, time : (new Date()).getTime()}; + window.mstag.loadTag("analytics", {dedup: "1", domainId: tarteaucitron.user.microsoftcampaignanalyticsdomainId, type: "1", actionid: tarteaucitron.user.microsoftcampaignanalyticsactionId}); + }); + } +}; + +// pinterest +tarteaucitron.services.pinterest = { + "key": "pinterest", + "type": "social", + "name": "Pinterest", + "uri": "https://about.pinterest.com/privacy-policy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['tacPinterest'], ''); + tarteaucitron.addScript('//assets.pinterest.com/js/pinit.js'); + }, + "fallback": function () { + "use strict"; + var id = 'pinterest'; + tarteaucitron.fallback(['tacPinterest'], tarteaucitron.engage(id)); + } +}; + +// prelinker +tarteaucitron.services.prelinker = { + "key": "prelinker", + "type": "ads", + "name": "Prelinker", + "uri": "http://www.prelinker.com/index/index/cgu/", + "needConsent": true, + "cookies": ['_sp_id.32f5', '_sp_ses.32f5'], + "js": function () { + "use strict"; + var uniqIds = [], + i, + uri; + + tarteaucitron.fallback(['prelinker-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + uri = 'http://promo.easy-dating.org/banner/index?'; + uri += 'site_id=' + document.getElementById(uniqIds[i]).getAttribute('siteId') + '&'; + uri += 'banner_id=' + document.getElementById(uniqIds[i]).getAttribute('bannerId') + '&'; + uri += 'default_language=' + document.getElementById(uniqIds[i]).getAttribute('defaultLanguage') + '&'; + uri += 'tr4ck=' + document.getElementById(uniqIds[i]).getAttribute('trackrt'); + + tarteaucitron.makeAsync.init(uri, uniqIds[i]); + } + }, + "fallback": function () { + "use strict"; + var id = 'prelinker'; + tarteaucitron.fallback(['prelinker-canvas'], tarteaucitron.engage(id)); + } +}; + +// prezi +tarteaucitron.services.prezi = { + "key": "prezi", + "type": "video", + "name": "Prezi", + "uri": "https://prezi.com/privacy-policy/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['prezi-canvas'], function (x) { + var id = x.getAttribute("data-id"), + width = x.getAttribute("width"), + height = x.getAttribute("height"), + url = 'https://prezi.com/embed/' + id + '/?bgcolor=ffffff&lock_to_path=0&autoplay=0&autohide_ctrls=0'; + + return ''; + }); + }, + "fallback": function () { + "use strict"; + var id = 'prezi'; + tarteaucitron.fallback(['prezi-canvas'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// pubdirecte +tarteaucitron.services.pubdirecte = { + "key": "pubdirecte", + "type": "ads", + "name": "Pubdirecte", + "uri": "http://pubdirecte.com/contact.php", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + var uniqIds = [], + i, + uri; + + tarteaucitron.fallback(['pubdirecte-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + uri = '//www.pubdirecte.com/script/banniere.php?'; + uri += 'id=' + document.getElementById(uniqIds[i]).getAttribute('pid') + '&'; + uri += 'ref=' + document.getElementById(uniqIds[i]).getAttribute('ref'); + + tarteaucitron.makeAsync.init(uri, uniqIds[i]); + } + }, + "fallback": function () { + "use strict"; + var id = 'pubdirecte'; + tarteaucitron.fallback(['pubdirecte-canvas'], tarteaucitron.engage(id)); + } +}; + +// purechat +tarteaucitron.services.purechat = { + "key": "purechat", + "type": "support", + "name": "PureChat", + "uri": "https://www.purechat.com/privacy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.purechatId === undefined) { + return; + } + + tarteaucitron.addScript('//app.purechat.com/VisitorWidget/WidgetScript', '', function () { + try { + window.w = new PCWidget({ c: tarteaucitron.user.purechatId, f: true }); + } catch (e) {} + }); + } +}; + +// shareaholic +tarteaucitron.services.shareaholic = { + "key": "shareaholic", + "type": "social", + "name": "Shareaholic", + "uri": "https://shareaholic.com/privacy/choices", + "needConsent": true, + "cookies": ['__utma', '__utmb', '__utmc', '__utmz', '__utmt_Shareaholic%20Pageviews'], + "js": function () { + "use strict"; + if (tarteaucitron.user.shareaholicSiteId === undefined) { + return; + } + + tarteaucitron.fallback(['shareaholic-canvas'], ''); + tarteaucitron.addScript('//dsms0mj1bbhn4.cloudfront.net/assets/pub/shareaholic.js', '', function () { + try { + Shareaholic.init(tarteaucitron.user.shareaholicSiteId); + } catch (e) {} + }); + }, + "fallback": function () { + "use strict"; + var id = 'shareaholic'; + tarteaucitron.fallback(['shareaholic-canvas'], tarteaucitron.engage(id)); + } +}; + +// shareasale +tarteaucitron.services.shareasale = { + "key": "shareasale", + "type": "ads", + "name": "ShareASale", + "uri": "https://www.shareasale.com/PrivacyPolicy.pdf", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + var uniqIds = [], + i, + uri; + + tarteaucitron.fallback(['shareasale-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + uri = 'https://shareasale.com/sale.cfm?'; + uri += 'amount=' + document.getElementById(uniqIds[i]).getAttribute('amount') + '&'; + uri += 'tracking=' + document.getElementById(uniqIds[i]).getAttribute('tracking') + '&'; + uri += 'transtype=' + document.getElementById(uniqIds[i]).getAttribute('transtype') + '&'; + uri += 'persale=' + document.getElementById(uniqIds[i]).getAttribute('persale') + '&'; + uri += 'perlead=' + document.getElementById(uniqIds[i]).getAttribute('perlead') + '&'; + uri += 'perhit=' + document.getElementById(uniqIds[i]).getAttribute('perhit') + '&'; + uri += 'merchantID=' + document.getElementById(uniqIds[i]).getAttribute('merchantID'); + + document.getElementById(uniqIds[i]).innerHTML = ''; + } + }, + "fallback": function () { + "use strict"; + var id = 'shareasale'; + tarteaucitron.fallback(['shareasale-canvas'], tarteaucitron.engage(id)); + } +}; + +// sharethis +tarteaucitron.services.sharethis = { + "key": "sharethis", + "type": "social", + "name": "ShareThis", + "uri": "http://www.sharethis.com/legal/privacy/", + "needConsent": true, + "cookies": ['__unam'], + "js": function () { + "use strict"; + if (tarteaucitron.user.sharethisPublisher === undefined) { + return; + } + var switchTo5x = true, + uri = ('https:' === document.location.protocol ? 'https://ws' : 'http://w') + '.sharethis.com/button/buttons.js'; + + tarteaucitron.fallback(['tacSharethis'], ''); + tarteaucitron.addScript(uri, '', function () { + stLight.options({publisher: tarteaucitron.user.sharethisPublisher, doNotHash: false, doNotCopy: false, hashAddressBar: false}); + }); + + if (tarteaucitron.isAjax === true) { + if (typeof stButtons !== "undefined") { + stButtons.locateElements(); + } + } + }, + "fallback": function () { + "use strict"; + var id = 'sharethis'; + tarteaucitron.fallback(['tacSharethis'], tarteaucitron.engage(id)); + } +}; + +// slideshare +tarteaucitron.services.slideshare = { + "key": "slideshare", + "type": "video", + "name": "SlideShare", + "uri": "https://www.linkedin.com/legal/privacy-policy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['slideshare-canvas'], function (x) { + var id = x.getAttribute("data-id"), + width = x.getAttribute("width"), + height = x.getAttribute("height"), + url = '//www.slideshare.net/slideshow/embed_code/' + id; + + return ''; + }); + }, + "fallback": function () { + "use strict"; + var id = 'slideshare'; + tarteaucitron.fallback(['slideshare-canvas'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// statcounter +tarteaucitron.services.statcounter = { + "key": "statcounter", + "type": "analytic", + "name": "StatCounter", + "uri": "https://fr.statcounter.com/about/legal/#privacy", + "needConsent": true, + "cookies": ['sc_is_visitor_unique'], + "js": function () { + "use strict"; + var uniqIds = [], + i, + uri = '//statcounter.com/counter/counter.js'; + + tarteaucitron.fallback(['statcounter-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + return '
'; + }); + + for (i = 0; i < uniqIds.length; i += 1) { + tarteaucitron.makeAsync.init(uri, uniqIds[i]); + } + }, + "fallback": function () { + "use strict"; + var id = 'statcounter'; + tarteaucitron.fallback(['statcounter-canvas'], tarteaucitron.engage(id)); + } +}; + +// timelinejs +tarteaucitron.services.timelinejs = { + "key": "timelinejs", + "type": "api", + "name": "Timeline JS", + "uri": "http://timeline.knightlab.com/#help", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['timelinejs-canvas'], function (x) { + var spreadsheet_id = x.getAttribute("spreadsheet_id"), + width = x.getAttribute("width"), + height = x.getAttribute("height"), + lang = x.getAttribute("lang_2_letter"), + font = x.getAttribute("font"), + map = x.getAttribute("map"), + start_at_end = x.getAttribute("start_at_end"), + hash_bookmark = x.getAttribute("hash_bookmark"), + start_at_slide = x.getAttribute("start_at_slide"), + start_zoom = x.getAttribute("start_zoom"), + url = '//cdn.knightlab.com/libs/timeline/latest/embed/index.html?source=' + spreadsheet_id + '&font=' + font + '&maptype=' + map + '&lang=' + lang + '&start_at_end=' + start_at_end + '&hash_bookmark=' + hash_bookmark + '&start_at_slide=' + start_at_slide + '&start_zoom_adjust=' + start_zoom + '&height=' + height; + + return ''; + }); + }, + "fallback": function () { + "use strict"; + var id = 'timelinejs'; + tarteaucitron.fallback(['timelinejs-canvas'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// typekit +tarteaucitron.services.typekit = { + "key": "typekit", + "type": "api", + "name": "Typekit (adobe)", + "uri": "http://www.adobe.com/fr/privacy.html", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.typekitId === undefined) { + return; + } + tarteaucitron.addScript('//use.typekit.net/' + tarteaucitron.user.typekitId + '.js', '', function () { + try { + Typekit.load(); + } catch (e) {} + }); + } +}; + +// twenga +tarteaucitron.services.twenga = { + "key": "twenga", + "type": "ads", + "name": "Twenga", + "uri": "http://www.twenga.com/privacy.php", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + + if (tarteaucitron.user.twengaId === undefined || tarteaucitron.user.twengaLocale === undefined) { + return; + } + + tarteaucitron.addScript('//tracker.twenga.' + tarteaucitron.user.twengaLocale + '/st/tracker_' + tarteaucitron.user.twengaId + '.js'); + } +}; + +// twitter +tarteaucitron.services.twitter = { + "key": "twitter", + "type": "social", + "name": "Twitter", + "uri": "https://support.twitter.com/articles/20170514", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['tacTwitter'], ''); + tarteaucitron.addScript('//platform.twitter.com/widgets.js', 'twitter-wjs'); + }, + "fallback": function () { + "use strict"; + var id = 'twitter'; + tarteaucitron.fallback(['tacTwitter'], tarteaucitron.engage(id)); + } +}; + +// twitter embed +tarteaucitron.services.twitterembed = { + "key": "twitterembed", + "type": "social", + "name": "Twitter (cards)", + "uri": "https://support.twitter.com/articles/20170514", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + var uniqIds = [], + i, + e, + html; + + tarteaucitron.fallback(['twitterembed-canvas'], function (x) { + var uniqId = '_' + Math.random().toString(36).substr(2, 9); + uniqIds.push(uniqId); + html = '
'; + return video_frame; + }); + }, + "fallback": function () { + "use strict"; + var id = 'vimeo'; + tarteaucitron.fallback(['vimeo_player'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// visualrevenue +tarteaucitron.services.visualrevenue = { + "key": "visualrevenue", + "type": "analytic", + "name": "VisualRevenue", + "uri": "http://www.outbrain.com/legal/privacy-713/", + "needConsent": true, + "cookies": ['__vrf', '__vrm', '__vrl', '__vry', '__vru', '__vrid', '__vrz'], + "js": function () { + "use strict"; + if (tarteaucitron.user.visualrevenueId === undefined) { + return; + } + window._vrq = window._vrq || []; + window._vrq.push(['id', tarteaucitron.user.visualrevenueId]); + window._vrq.push(['automate', true]); + window._vrq.push(['track', function () {}]); + tarteaucitron.addScript('http://a.visualrevenue.com/vrs.js'); + } +}; + +// vshop +tarteaucitron.services.vshop = { + "key": "vshop", + "type": "ads", + "name": "vShop", + "uri": "http://vshop.fr/privacy-policy", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + tarteaucitron.fallback(['vcashW'], ''); + tarteaucitron.addScript('//vshop.fr/js/w.js'); + }, + "fallback": function () { + "use strict"; + var id = 'vshop'; + tarteaucitron.fallback(['vcashW'], tarteaucitron.engage(id)); + } +}; + +// wysistat +tarteaucitron.services.wysistat = { + "key": "wysistat", + "type": "analytic", + "name": "Wysistat", + "uri": "http://wysistat.net/contact/", + "needConsent": true, + "cookies": ['Wysistat'], + "js": function () { + "use strict"; + if (tarteaucitron.user.wysistat === undefined) { + return; + } + tarteaucitron.addScript('//www.wysistat.com/statistique.js', '', function () { + window.stat(tarteaucitron.user.wysistat.cli, tarteaucitron.user.wysistat.frm, tarteaucitron.user.wysistat.prm, tarteaucitron.user.wysistat.ce, tarteaucitron.user.wysistat.page, tarteaucitron.user.wysistat.roi, tarteaucitron.user.wysistat.prof, tarteaucitron.user.wysistat.cpt); + }); + } +}; + +// xiti +tarteaucitron.services.xiti = { + "key": "xiti", + "type": "analytic", + "name": "Xiti", + "uri": "http://www.atinternet.com/politique-du-respect-de-la-vie-privee/", + "needConsent": true, + "cookies": [], + "js": function () { + "use strict"; + if (tarteaucitron.user.xitiId === undefined) { + return; + } + var Xt_param = 's=' + tarteaucitron.user.xitiId + '&p=', + Xt_r, + Xt_h, + Xt_i, + Xt_s, + div = document.createElement('div'); + try { + Xt_r = top.document.referrer; + } catch (e) { + Xt_r = document.referrer; + } + Xt_h = new Date(); + Xt_i = '= 4) { + Xt_s = screen; + Xt_i += '&r=' + Xt_s.width + 'x' + Xt_s.height + 'x' + Xt_s.pixelDepth + 'x' + Xt_s.colorDepth; + } + + div.innerHTML = Xt_i + '&ref=' + Xt_r.replace(/[<>"]/g, '').replace(/&/g, '$') + '" title="Internet Audience">'; + document.getElementsByTagName('body')[0].appendChild(div.firstChild); + + if (typeof tarteaucitron.user.xitiMore === 'function') { + tarteaucitron.user.xitiMore(); + } + } +}; + +// youtube +tarteaucitron.services.youtube = { + "key": "youtube", + "type": "video", + "name": "YouTube", + "uri": "https://www.google.fr/intl/fr/policies/privacy/", + "needConsent": true, + "cookies": ['VISITOR_INFO1_LIVE', 'YSC', 'PREF', 'GEUP'], + "js": function () { + "use strict"; + tarteaucitron.fallback(['youtube_player'], function (x) { + var video_id = x.getAttribute("videoID"), + video_width = x.getAttribute("width"), + frame_width = 'width=', + video_height = x.getAttribute("height"), + frame_height = 'height=', + video_frame, + params = 'theme=' + x.getAttribute("theme") + '&rel=' + x.getAttribute("rel") + '&controls=' + x.getAttribute("controls") + '&showinfo=' + x.getAttribute("showinfo") + '&autoplay=' + x.getAttribute("autoplay"); + + if (video_id === undefined) { + return ""; + } + if (video_width !== undefined) { + frame_width += '"' + video_width + '" '; + } else { + frame_width += '"" '; + } + if (video_height !== undefined) { + frame_height += '"' + video_height + '" '; + } else { + frame_height += '"" '; + } + video_frame = ''; + return video_frame; + }); + }, + "fallback": function () { + "use strict"; + var id = 'youtube'; + tarteaucitron.fallback(['youtube_player'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// youtube playlist +tarteaucitron.services.youtubeplaylist = { + "key": "youtubeplaylist", + "type": "video", + "name": "YouTube (playlist)", + "uri": "https://www.google.fr/intl/fr/policies/privacy/", + "needConsent": true, + "cookies": ['VISITOR_INFO1_LIVE', 'YSC', 'PREF', 'GEUP'], + "js": function () { + "use strict"; + tarteaucitron.fallback(['youtube_playlist_player'], function (x) { + var playlist_id = x.getAttribute("playlistID"), + video_width = x.getAttribute("width"), + frame_width = 'width=', + video_height = x.getAttribute("height"), + frame_height = 'height=', + video_frame, + params = 'theme=' + x.getAttribute("theme") + '&rel=' + x.getAttribute("rel") + '&controls=' + x.getAttribute("controls") + '&showinfo=' + x.getAttribute("showinfo") + '&autoplay=' + x.getAttribute("autoplay"); + + if (playlist_id === undefined) { + return ""; + } + if (video_width !== undefined) { + frame_width += '"' + video_width + '" '; + } else { + frame_width += '"" '; + } + if (video_height !== undefined) { + frame_height += '"' + video_height + '" '; + } else { + frame_height += '"" '; + } + video_frame = ''; + return video_frame; + }); + }, + "fallback": function () { + "use strict"; + var id = 'youtubeplaylist'; + tarteaucitron.fallback(['youtube_playlist_player'], function (elem) { + elem.style.width = elem.getAttribute('width') + 'px'; + elem.style.height = elem.getAttribute('height') + 'px'; + return tarteaucitron.engage(id); + }); + } +}; + +// zopim +tarteaucitron.services.zopim = { + "key": "zopim", + "type": "support", + "name": "Zopim", + "uri": "https://www.zopim.com/privacy", + "needConsent": true, + "cookies": ['__zlcid', '__zprivacy'], + "js": function () { + "use strict"; + if (tarteaucitron.user.zopimID === undefined) { + return; + } + tarteaucitron.addScript('//v2.zopim.com/?' + tarteaucitron.user.zopimID); + } +}; + +// xiti smartTag +tarteaucitron.services.xiti_smarttag = { + "key": "xiti_smarttag", + "type": "analytic", + "name": "Xiti (SmartTag)", + "uri": "https://www.atinternet.com/societe/protection-des-donnees/", + "needConsent": true, + "cookies": ["atidvisitor", "atreman", "atredir", "atsession", "atuserid", "attvtreman", "attvtsession"], + "js": function () { + "use strict"; + if (tarteaucitron.user.xiti_smarttagLocalPath !== undefined) { + tarteaucitron.addScript(tarteaucitron.user.xiti_smarttagLocalPath, 'smarttag', null, null, "onload", "addTracker();"); + } else { + var xitiSmarttagId = tarteaucitron.user.xiti_smarttagSiteId; + if (xitiSmarttagId === undefined) { + return; + } + + tarteaucitron.addScript('//tag.aticdn.net/' + xitiSmarttagId + '/smarttag.js', 'smarttag', null, null, "onload", "addTracker();"); + } + } +}; + +// facebook pixel +tarteaucitron.services.facebookpixel = { + "key": "facebookpixel", + "type": "ads", + "name": "Facebook Pixel", + "uri": "https://fr-fr.facebook.com/business/help/www/651294705016616", + "needConsent": true, + "cookies": ['datr', 'fr', 'reg_ext_ref', 'reg_fb_gate', 'reg_fb_ref', 'sb', 'wd', 'x-src'], + "js": function () { + "use strict"; + var n; + if(window.fbq)return; + n=window.fbq=function(){n.callMethod? n.callMethod.apply(n,arguments):n.queue.push(arguments)} ; + if(!window._fbq)window._fbq=n; + n.push=n; + n.loaded=!0; + n.version='2.0'; + n.queue=[]; + tarteaucitron.addScript('https://connect.facebook.net/en_US/fbevents.js'); + fbq('init', tarteaucitron.user.facebookpixelId); + fbq('track', 'PageView'); + + if (typeof tarteaucitron.user.facebookpixelMore === 'function') { + tarteaucitron.user.facebookpixelMore(); + } + } +}; \ No newline at end of file diff --git a/docs/pages/home.md b/docs/pages/home.md new file mode 100644 index 0000000..ced7e4d --- /dev/null +++ b/docs/pages/home.md @@ -0,0 +1,18 @@ +

+ meteole +

+

+ Easy access to Météo-France weather models and data +

+

+ Versions +

+ +# Welcome to the documentation + +* Want to know why does **Meteole** exist? :arrow_right: [Why use Meteole?](why.md) +* Want to get started? :arrow_right: [User Guide](how_to.md) + +**Meteole** is automatically tested with: + +![Alt Python](https://img.shields.io/pypi/pyversions/meteole) \ No newline at end of file diff --git a/docs/pages/how_to.md b/docs/pages/how_to.md new file mode 100644 index 0000000..27d1774 --- /dev/null +++ b/docs/pages/how_to.md @@ -0,0 +1,144 @@ +## Installation + +Ensure that you have correctly installed **Meteole** before (check [Installation page](installation.md) for details) + +```python +pip install meteole +``` + +## Get a token, an API key or an application ID + +1. Create an account on [the Météo-France API portal](https://portail-api.meteofrance.fr/). +2. Subscribe to the desired services (Arome, Arpege, etc.). +3. Retrieve the API token (or key) by going to “Mes APIs” and then Générer token”. + +> 💡 +> +> Using an APPLICATION_ID allows for token auto-refresh. It avoids re-generating a token or an API key when it is expired. +> +> Find your APPLICATION_ID in your [API dashboard](https://portail-api.meteofrance.fr/web/fr/dashboard) > "Générer Token". +> +> Then checkout the `curl` field at the bottom of the page that looks like that: +> ```bash +> curl -k -X POST https://portail-api.meteofrance.fr/token -d "grant_type=client_credentials" -H "Authorization: Basic ktDvFBDP8w6jGfKuK4yB1nS6oLOK4bfoFwEqmANOIvNMF8vG6B51tgJeZQcOO1d3qYyK" +> ``` +> +> The string that comes rights after "Basic" is your APPLICATION_ID (`ktDvFBDP8w6jGfKuK4yB1nS6oLOK4bfoFwEqmANOIvNMF8vG6B51tgJeZQcOO1d3qYyK` in this example) + +## Get the latest vigilance bulletin + +Meteo France offers a vigilance bulletin that provides nationwide predictions of potential weather risks. + +For data usage, access the predicted phenomena to trigger modeling based on the forecasts. + +```python +from meteole import Vigilance + +# application_id: get it on Météo-France portal +client = Vigilance(application_id=APPLICATION_ID) + +df_phenomenon, df_timelaps = client.get_phenomenon() + +# Fetch vigilance bulletins +textes_vigilance = client.get_textes_vigilance() + +# Display the vigilance vignette +client.get_vignette() +``` + +![bulletin vigilance](./assets/img/png/vignette_exemple.png) + +> More details about Vigilance Bulletin in [the official Meteo France Documentation](https://donneespubliques.meteofrance.fr/?fond=produit&id_produit=305&id_rubrique=50) + +## Get AROME or ARPEGE data + +The flagship weather forecasting models of Météo-France are accessible via the Météo-France APIs. + +| Characteristics | AROME | ARPEGE | +|------------------|----------------------|----------------------| +| Resolution | 1.3 km | 10 km | +| Update Frequency | Every 3 hours | Every 6 hours | +| Forecast Range | Up to 51 hours | Up to 114 hours | + +```python +from meteole import arome + +arome_client = arome.AromeForecast(application_id=APPLICATION_ID) # api_key found on portail.meteo-france.Fr + +# get all available coverages +# coverage: a string containing indicator + run +capabilities = arome_client.get_capabilities() + +# fetch a valid coverage_id for WIND_GUST +indicator = 'V_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND' +coverage_id = capabilities[capabilities['indicator'] == indicator]['id'].iloc[0] + +# get the data +# (params heights and forecast_horizons default to their first allowed value) +df_arome = arome_client.get_coverage(coverage_id) +``` + +## Advanced guide: coverages + +### Introduction + +Understanding coverages is a must to have a comprehensive usage of Météo-France forecasting models like AROME or ARPEGE. + +A coverageid looks like that: + +> WIND_SPEED__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___2024-01-16T09.00.00Z + +It contains several information in a single string: + +- WIND_SPEED: Indicates that the data pertains to wind speed. +- SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND: Specifies that the measurement is taken at a particular height above the ground. +- 2024-01-16T09.00.00Z: Represents the date and time of the measurement, in ISO 8601 format (January 16, 2024, at 09:00 UTC). + +### Time-series coverages + +Some coverages can contain an additional suffix: + +> TEMPERATURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___2024-01-16T09.00.00Z_PT1H + +`PT1H` Specifies the interval, meaning the data is provided at 1-hour intervals. + +When no interval is specified, it means coverage returns a single datapoint instead of a timeseries. + +### Height + +Atmospheric parameters can be measured at various heights and pressure levels, providing comprehensive data for weather analysis and forecasting. In consequence, some coverages must be queried with a `height` parameter. + +To get the list of available `height` parameters, use the function `get_coverage_description` as described in the example below. + +```python +from meteole import arome + +arome_client = arome.AromeForecast(application_id=APPLICATION_ID) # api_key found on portail.meteo-france.Fr + +# get all available coverage ids with `get_capabilities` +capabilities = arome_client.get_capabilities() + +# fetch a valid coverage_id for WIND_GUST +indicator = 'V_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND' +coverage_id = capabilities[capabilities['indicator'] == indicator]['id'].iloc[0] + +# get the description of the coverage +coverage_axis = arome.get_coverage_description(random_coverage_id) + +# retrieve the available heights +coverage_axis['heights'] +``` + +Similarly, the AROME and ARPEGE can have different time step forecast prediction depending on the indicator. + +For example: + +- `TODO` is defined every horu for the next 114 hours. +- `TODO` is defined every hour for the next 51 hours, and then every 3 hours. + +Get the list of the available `forecast_horizons` using, once again, `get_coverage_description`. + +```python +# retrieve the available times +coverage_axis['times'] +``` diff --git a/docs/pages/index.html b/docs/pages/index.html new file mode 100644 index 0000000..f46aa61 --- /dev/null +++ b/docs/pages/index.html @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + Meteole by MAIF + + + + + +
+ +
+
+
+ +
+
+
+
    +
  • Easy access
  • +
  • to Météo-France
  • +
  • weather models
  • +
  • and data
  • +
+

+ Meteole provides a python wrapper around Météo-France APIs to facilitate models exploration and data retrieval. +

+
+

Features

+
    +
  • Météo-France API token management
  • +
  • Arome/Arpege forecasts
  • +
  • Vigilance bulletins
  • +
+
+
+
+ logo +

Concise

+

Get weather data with two lines of code. +

+ logo +

high adaptability

+

Painless deepdiving into weather model parameters.

+ +
+
+
+
+
+
+
+ +
+
+ + + + + \ No newline at end of file diff --git a/docs/pages/installation.md b/docs/pages/installation.md new file mode 100644 index 0000000..a8e4aa5 --- /dev/null +++ b/docs/pages/installation.md @@ -0,0 +1,21 @@ +## Python + +Compatible with: + +![Alt Versions](https://img.shields.io/pypi/v/meteole) + +## pip + +In your python environment: + +### Regular use + +```shell +pip install meteole +``` + +### Development + +```shell +pip install meteole[all] +``` diff --git a/docs/pages/why.md b/docs/pages/why.md new file mode 100644 index 0000000..a3f1457 --- /dev/null +++ b/docs/pages/why.md @@ -0,0 +1,23 @@ +There is one reason for using **Meteole**: + +> Easy access Météo-France forecast and vigilance data + +## Before Meteole :cloud_with_rain: + +Accessing weather data from Météo-France via Python can be a bit cumbersome and time-consuming. + +It involves: + +* diving into Météo-France documentation +* handling expiring tokens +* managing XML responses +* explore specificities for every indicator +* parse responses to get a clean dataframe + +## After Meteole :sunny: + +**Meteole** allows to quickly fetch tidy pandas dataframes full of weather-rich data: + +* By handling the API boilerplate; +* By providing simple and well-documented function; +* By using heuristics when necessary parameters are not provided ; diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..4484a69 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,75 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[project] +name = "meteole" +version = "0.2.0" +description = "Wrapper around Méteo-France Public API" +readme = "README.md" +authors = [ + {name = "Squad Geodatahub / Tribu IODA"}, +] + +classifiers = [ # + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + # "Programming Language :: Python :: 3.12", no wheels 3.12 for dependency ecmwflibs + "Topic :: Software Development :: Libraries :: Python Modules", +] + +dependencies = [ + "pandas>=2.0.0", + "ecmwflibs>=0.6.3", + "cfgrib>=0.0.11.0", + "numpy>=1.26.4", + "rasterio>=1.3.10", + "requests>=2.31.0", + "tqdm>=4.66.4", + "xarray>=2024.5.0", + "xmltodict>=0.13.0", + "cartopy>=0.23.0", + "matplotlib>=3.8.4", +] + +[project.optional-dependencies] +test = ["pytest", "coverage"] +doc = ["mkdocs-material", "mkdocstrings[python]"] +mypy = ["mypy"] +ruff = ["ruff"] +all = [ + "meteole[test]", + "meteole[doc]", + "meteole[mypy]", + "meteole[ruff]", + "pre-commit", +] + +[tool.setuptools] +package-dir = { "" = "src" } # + +[tool.pytest.ini_options] +pythonpath = ["src"] # +testpaths = ["tests"] + +[tool.mypy] +exclude = ["tests"] +ignore_missing_imports = true + +[tool.ruff] +line-length = 120 +exclude = ["tests"] +extend-include = ["*.ipynb"] + +[tool.ruff.lint] +ignore = ["E501", "D2", "D3", "D4", "D104", "D100", "D106", "S311"] +extend-select = [ + "UP", # pyupgrade" + "S", # flake8-bandit, + "B", # flake8-bugbear + "I", # isort + "D", # pydocstyle + "NPY", # NumPy-specific rules +] diff --git a/src/meteole/__init__.py b/src/meteole/__init__.py new file mode 100644 index 0000000..3a315d8 --- /dev/null +++ b/src/meteole/__init__.py @@ -0,0 +1,31 @@ +from meteole.arome import AromeForecast # noqa +from meteole.arpege import ArpegeForecast # noqa +from meteole.vigilance import Vigilance # noqa + +from importlib.metadata import version + +__version__ = version("meteole") + + +import logging + + +def setup_logger(): + """Setup logger with proper StreamHandler and formatter""" + logger = logging.getLogger(__name__) + logger.setLevel(logging.INFO) + + # Créer un gestionnaire de flux (StreamHandler) pour afficher les logs dans la console + handler = logging.StreamHandler() + handler.setLevel(logging.INFO) + + # Créer un formatteur et l'ajouter au gestionnaire + # formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + formatter = logging.Formatter("%(message)s") + handler.setFormatter(formatter) + + # Ajouter le gestionnaire au logger + return logger.addHandler(handler) + + +logger = setup_logger() diff --git a/src/meteole/arome.py b/src/meteole/arome.py new file mode 100644 index 0000000..01e3d28 --- /dev/null +++ b/src/meteole/arome.py @@ -0,0 +1,121 @@ +"""The interface for the observational data from the meteo-France API. + +See : +- https://portail-api.meteofrance.fr/web/fr/api/arome +""" + +import logging + +from meteole import const, forecast + +logger = logging.getLogger(__name__) + +AVAILABLE_AROME_TERRITORY = [ + "FRANCE", + "NCALED", + "INDIEN", + "POLYN", + "GUYANE", + "ANTIL", +] + +AROME_INSTANT_INDICATORS = [ + "GEOMETRIC_HEIGHT__GROUND_OR_WATER_SURFACE", + "BRIGHTNESS_TEMPERATURE__GROUND_OR_WATER_SURFACE", + "CONVECTIVE_AVAILABLE_POTENTIAL_ENERGY__GROUND_OR_WATER_SURFACE", + "WIND_SPEED_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "WIND_SPEED__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "RELATIVE_HUMIDITY__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "LOW_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "HIGH_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "MEDIUM_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "PRESSURE__GROUND_OR_WATER_SURFACE", + "TOTAL_PRECIPITATION_RATE__GROUND_OR_WATER_SURFACE", + "TEMPERATURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "U_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "U_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "V_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "V_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", +] + +AROME_OTHER_INDICATORS = [ + "TOTAL_WATER_PRECIPITATION__GROUND_OR_WATER_SURFACE", + "TOTAL_SNOW_PRECIPITATION__GROUND_OR_WATER_SURFACE", + "TOTAL_PRECIPITATION__GROUND_OR_WATER_SURFACE", +] + + +class AromeForecast(forecast.Forecast): + """Access the AROME numerical Forecast.""" + + api_version = "1.0" + base_url = const.API_BASE_URL + "arome/" + api_version + + def __init__( + self, + api_key: str | None = None, + territory: str = "FRANCE", + precision: float = 0.01, + token: str | None = None, + application_id: str | None = None, + cache_dir: str | None = None, + ): + """ + Init the AromeForecast object. + + Parameters + ---------- + precision : {0.01, 0.025}, optional + the resolution of the AROME Model, by default 0.01 + territory : str, optional + The AROME territory to fetch, by default "FRANCE" + api_key : str | None, optional + The API Key, by default None + token : str | None, optional + The API Token, by default None + application_id : str | None, optional + The Application ID, by default None + cache_dir : str | None, optional + The path to the caching directory, by default None. + If None, the cache directory is set to "/tmp/cache". + + Note + ---- + See :class:`.MeteoFranceClient` for the parameters `api_key`, `token` and `application_id`. + + The available territories are listed in :data:`.AVAILABLE_TERRITORY`. + + """ + super().__init__(api_key, token, territory, precision, application_id, cache_dir) + + def _validate_parameters(self): + """Assert the parameters are valid.""" + if self.precision not in [0.01, 0.025]: + raise ValueError("Parameter `precision` must be in (0.01, 0.025). It is inferred from argument `territory`") + if self.territory not in AVAILABLE_AROME_TERRITORY: + raise ValueError(f"Parameter `territory` must be in {AVAILABLE_AROME_TERRITORY}") + + @property + def run_frequency(self): + """Update frequency of the inference""" + return 3 + + @property + def model_name(self): + """Name of the model (lower case)""" + return "arome" + + @property + def entry_point(self): + """The entry point to AROME service.""" + return f"wcs/MF-NWP-HIGHRES-AROME-{const.PRECISION_FLOAT_TO_STR[self.precision]}-{self.territory}-WCS" + + @property + def indicators(self): + """List of all indicators (instant and non-instant)""" + return AROME_INSTANT_INDICATORS + AROME_OTHER_INDICATORS + + @property + def instant_indicators(self): + """List of instant indicators""" + return AROME_INSTANT_INDICATORS diff --git a/src/meteole/arpege.py b/src/meteole/arpege.py new file mode 100644 index 0000000..fb2eeb8 --- /dev/null +++ b/src/meteole/arpege.py @@ -0,0 +1,142 @@ +"""The interface for the observational data from the meteo-France API. + +See : +- https://portail-api.meteofrance.fr/web/fr/api/arpege +""" + +from meteole import const, forecast + +AVAILABLE_ARPEGE_TERRITORY = ["EUROPE", "GLOBE", "ATOURX", "EURAT"] +RELATION_TERRITORY_TO_PREC_ARPEGE = {"EUROPE": 0.1, "GLOBE": 0.25, "ATOURX": 0.1, "EURAT": 0.05} + +ARPEGE_INSTANT_INDICATORS = [ + "GEOMETRIC_HEIGHT__GROUND_OR_WATER_SURFACE", + "BRIGHTNESS_TEMPERATURE__GROUND_OR_WATER_SURFACE", + "CONVECTIVE_AVAILABLE_POTENTIAL_ENERGY__GROUND_OR_WATER_SURFACE", + "SPECIFIC_CLOUD_ICE_WATER_CONTENT__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "SPECIFIC_CLOUD_ICE_WATER_CONTENT__ISOBARIC_SURFACE", + "WIND_SPEED_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "WIND_SPEED__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "WIND_SPEED__ISOBARIC_SURFACE", + "DOWNWARD_SHORT_WAVE_RADIATION_FLUX__GROUND_OR_WATER_SURFACE", + "SHORT_WAVE_RADIATION_FLUX__GROUND_OR_WATER_SURFACE", + "RELATIVE_HUMIDITY__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "RELATIVE_HUMIDITY__ISOBARIC_SURFACE", + "PLANETARY_BOUNDARY_LAYER_HEIGHT__GROUND_OR_WATER_SURFACE", + "LOW_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "HIGH_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "MEDIUM_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "PRESSURE__GROUND_OR_WATER_SURFACE", + "PRESSURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "PRESSURE__MEAN_SEA_LEVEL", + "ABSOLUTE_VORTICITY__ISOBARIC_SURFACE", + "DEW_POINT_TEMPERATURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "DEW_POINT_TEMPERATURE__ISOBARIC_SURFACE", + "TURBULENT_KINETIC_ENERGY__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "TURBULENT_KINETIC_ENERGY__ISOBARIC_SURFACE", + "MAXIMUM_TEMPERATURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "MINIMUM_TEMPERATURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "PSEUDO_ADIABATIC_POTENTIAL_TEMPERATURE__ISOBARIC_SURFACE", + "POTENTIAL_VORTICITY__ISOBARIC_SURFACE", + "TEMPERATURE__GROUND_OR_WATER_SURFACE", + "TEMPERATURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "TEMPERATURE__ISOBARIC_SURFACE", + "U_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "U_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "U_COMPONENT_OF_WIND__ISOBARIC_SURFACE", + "U_COMPONENT_OF_WIND__POTENTIAL_VORTICITY_SURFACE_1500", + "U_COMPONENT_OF_WIND__POTENTIAL_VORTICITY_SURFACE_2000", + "VERTICAL_VELOCITY_PRESSURE__ISOBARIC_SURFACE", + "V_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "V_COMPONENT_OF_WIND__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND", + "V_COMPONENT_OF_WIND__ISOBARIC_SURFACE", + "V_COMPONENT_OF_WIND__POTENTIAL_VORTICITY_SURFACE_1500", + "V_COMPONENT_OF_WIND__POTENTIAL_VORTICITY_SURFACE_2000", + "GEOPOTENTIAL__ISOBARIC_SURFACE", +] + +ARPEGE_OTHER_INDICATORS = [ + "TOTAL_WATER_PRECIPITATION__GROUND_OR_WATER_SURFACE", + "TOTAL_CLOUD_COVER__GROUND_OR_WATER_SURFACE", + "TOTAL_SNOW_PRECIPITATION__GROUND_OR_WATER_SURFACE", + "TOTAL_PRECIPITATION__GROUND_OR_WATER_SURFACE", +] + + +class ArpegeForecast(forecast.Forecast): + """Access the ARPEGE numerical Forecast.""" + + api_version = "1.0" + base_url = const.API_BASE_URL + "arpege/" + api_version + + def __init__( + self, + territory: str = "EUROPE", + api_key: str | None = None, + token: str | None = None, + application_id: str | None = None, + cache_dir: str | None = None, + ): + """ + Init the ArpegeForecast object. + Note that `precision` is infered from `territory`. + + Parameters + ---------- + api_key : str | None, optional + The API Key, by default None + token : str | None, optional + The API Token, by default None + territory : str, optional + The ARPEGE territory to fetch, by default "FRANCE" + application_id : str | None, optional + The Application ID, by default None + cache_dir : str | None, optional + The path to the caching directory, by default None. + If None, the cache directory is set to "/tmp/cache". + + Note + ---- + See :class:`.MeteoFranceClient` for the parameters `api_key`, `token` and `application_id`. + + The available territories are listed in :data:`.AVAILABLE_TERRITORY`. + + """ + super().__init__( + api_key=api_key, + token=token, + territory=territory, + precision=RELATION_TERRITORY_TO_PREC_ARPEGE[territory], + application_id=application_id, + cache_dir=cache_dir, + ) + + def _validate_parameters(self): + """Assert the parameters are valid.""" + if self.territory not in AVAILABLE_ARPEGE_TERRITORY: + raise ValueError(f"The parameter precision must be in {AVAILABLE_ARPEGE_TERRITORY}") + + @property + def model_name(self): + """Name of the model (lower case)""" + return "arpege" + + @property + def run_frequency(self): + """Update frequency of the inference""" + return 6 + + @property + def entry_point(self): + """Entry point to ARPEGE service.""" + return f"wcs/MF-NWP-GLOBAL-ARPEGE-{const.PRECISION_FLOAT_TO_STR[self.precision]}-{self.territory}-WCS" + + @property + def indicators(self): + """List of all indicators (instant and non-instant)""" + return ARPEGE_INSTANT_INDICATORS + ARPEGE_OTHER_INDICATORS + + @property + def instant_indicators(self): + """List of instant indicators""" + return ARPEGE_INSTANT_INDICATORS diff --git a/src/meteole/client.py b/src/meteole/client.py new file mode 100644 index 0000000..1a59516 --- /dev/null +++ b/src/meteole/client.py @@ -0,0 +1,166 @@ +"""Core module for the MeteoFranceClient package.""" + +import logging +import os +import tempfile +import time +from pathlib import Path + +import requests + +from meteole.const import ( + EXPIRED_TOKEN_CODE, + MISSING_DATA_CODE, + PARAMETER_ERROR_CODE, + SPECIFIC_ERROR, + SUCCESS_CODES, +) +from meteole.errors import MissingDataError, MissingParameterError + +logger = logging.getLogger(__name__) + + +class MeteoFranceClient: + """Handles the connection and token refreshment boilerplate""" + + def __init__( + self, + api_key: str | None = None, + token: str | None = None, + application_id: str | None = None, + ): + """Init the MeteoFranceClient object.""" + self.api_key = api_key + self.token = token + self.application_id = application_id + + self.session = requests.Session() + self.connect() + + def connect(self): + """Connect to the MeteoFrance API. + + If the API key is provided, it is used to authenticate the user. + If the token is provided, it is used to authenticate the user. + If the application ID is provided, a token is requested from the API. + """ + if self.api_key is None and self.token is None: + if self.application_id is None: + raise ValueError("api_key or token or application_id must be provided") + self.token = self.get_token() + if self.api_key is not None: + logger.debug("using api key") + self.session.headers.update({"apikey": self.api_key}) + else: + logger.debug("using token") + self.session.headers.update({"Authorization": f"Bearer {self.token}"}) + + def get_token(self): + """request a token from the meteo-France API. + + The token lasts 1 hour, and is used to authenticate the user. + If a new token is requested before the previous one expires, the previous one is invalidated. + A local cache is used to avoid requesting a new token at each run of the script. + """ + # cache the token for 1 hour + TOKEN_DURATION_S = 3600 + local_tmp_cache = tempfile.TemporaryDirectory().name + cache_filename = Path(local_tmp_cache) / "token.txt" + cache_time_filename = Path(local_tmp_cache) / "token_time.txt" + + # try to read from cache + if cache_filename.exists() and cache_time_filename.exists(): + logger.debug("reading token from cache") + with open(cache_time_filename) as f: + cache_time = float(f.read()) + if cache_time >= (time.time() - TOKEN_DURATION_S): + with open(cache_filename) as f: + token = f.read() + return token + + token_entrypoint = "https://portail-api.meteofrance.fr/token" # noqa: S105 + params = {"grant_type": "client_credentials"} + header = {"Authorization": "Basic " + self.application_id} + res = requests.post(token_entrypoint, params=params, headers=header, timeout=(30, 3600)) + self.token = res.json()["access_token"] + + # save token to file + Path(local_tmp_cache).mkdir(parents=True, exist_ok=True) + with open(cache_filename, "w") as f: + f.write(self.token) + with open(cache_time_filename, "w") as f: + f.write(str(time.time())) + return self.token + + def _get_request(self, url, params=None, max_retries=5): + """Make a get request to the API. + + Parameters + ---------- + url : str + the url to request + params : dict + the parameters to pass to the request + max_retries : int + the maximum number of retries + + Returns + ------- + requests.Response + the response of the request + """ + logger.debug(f"GET {url}") + path_certif = str(Path(__file__).parents[0] / "utils/cacert.pem") + attempt = 0 + while attempt < max_retries: + if os.path.isfile(path_certif): + res = self.session.get(url, params=params, verify=path_certif) + else: + res = self.session.get(url, params=params) + if self._token_expired(res): + logger.info("token expired, requesting a new one") + self.get_token() + self.connect() + if os.path.isfile(path_certif): + res = self.session.get(url, params=params, verify=path_certif) + else: + res = self.session.get(url, params=params) + return res + + error_code = res.status_code + if error_code in SUCCESS_CODES: + logger.debug("request successful") + return res + if error_code == PARAMETER_ERROR_CODE: + logger.error("parameter error") + raise MissingParameterError(res.text) + if error_code == MISSING_DATA_CODE: + logger.error("missing data") + raise MissingDataError(res.text) + if error_code == SPECIFIC_ERROR: + logger.error("Erreur code status 502") + time.sleep(5) + attempt += 1 + logger.info(f"Retrying... Attempt {attempt} of {max_retries}") + continue + + break + + raise ValueError("Failed to get a successful response after retries") + + @staticmethod + def _token_expired(res): + """Check if the token is expired. + + Returns + ------- + bool + True if the token is expired, False otherwise. + """ + status = res.status_code + if status == EXPIRED_TOKEN_CODE: + if "application/json" in res.headers["Content-Type"]: + data = res.json() + if "Invalid JWT token" in data["description"]: + return True + return False diff --git a/src/meteole/const.py b/src/meteole/const.py new file mode 100644 index 0000000..795ca1e --- /dev/null +++ b/src/meteole/const.py @@ -0,0 +1,30 @@ +API_BASE_URL = "https://public-api.meteofrance.fr/public/" + +EXPIRED_TOKEN_CODE = 401 +SUCCESS_CODES = [200, 202, 201] +PARAMETER_ERROR_CODE = 400 +INVALID_TOKEN_CODE = 401 +FORBIDDEN_CODE = 403 +THROTTLED_CODE = 429 +INTERNAL_ERROR_CODE = 500 +UNUVAILABLE_CODE = 503 +BACKEND_ERROR_CODE = 504 +MISSING_DATA_CODE = 404 +SPECIFIC_ERROR = 502 + +PRECISION_FLOAT_TO_STR = {0.25: "025", 0.1: "01", 0.05: "005", 0.01: "001", 0.025: "0025"} + +FRANCE_METRO_LONGITUDES = (-5.1413, 9.5602) +FRANCE_METRO_LATITUDES = (41.33356, 51.0889) + +dict_phenomenon_id = { + "1": "vent", + "2": "pluie", + "3": "orages", + "4": "crues", + "5": "neige / verglas", + "6": "canicule", + "7": "grand froid", + "8": "avalanches", + "9": "vagues submersion", +} diff --git a/src/meteole/errors.py b/src/meteole/errors.py new file mode 100644 index 0000000..46fd6f6 --- /dev/null +++ b/src/meteole/errors.py @@ -0,0 +1,58 @@ +import xmltodict + + +class MissingParameterError(Exception): + """Exception raised errors in the input parameters where a required field is missing. + + Parameters + ---------- + message : str + Human-readable string descipting the exceptetion. + description : str + More detailed description of the error.""" + + def init(self, text: str): + """Initialize the exception with an error message parsed from an XML + string. + + Parameters + ---------- + text str: + XML string containing the error details, + expected to follow a specific schema with 'am:fault' as the root + element and 'am:message' and 'am:description' as child elements.""" + + # parse the error message with xmltodict + data = xmltodict.parse(text) + message = data["am:fault"]["am:message"] + description = data["am:fault"]["am:description"] + self.message = f"{message}\n {description}" + super().__init__(self.message) + + +class MissingDataError(Exception): + """Exception raised errors in the input data is missing""" + + def init(self, text: str): + """Initialize the exception with an error message parsed from an XML + string. + + Parameters + ---------- + text str: + XML string containing the error details, + expected to follow a specific schema with 'am:fault' as the root + element and 'am:message' and 'am:description' as child elements.""" + + # parse the error message with xmltodict + try: + data = xmltodict.parse(text) + exception = data["mw:fault"]["mw:description"]["ns0:ExceptionReport"]["ns0:Exception"] + code = exception["@exceptionCode"] + locator = exception["@locator"] + text = exception["ns0:ExceptionText"] + message = f"Error code: {code}\nLocator: {locator}\nText: {text}" + except Exception: + message = text + self.message = message + super().__init__(self.message) diff --git a/src/meteole/forecast.py b/src/meteole/forecast.py new file mode 100644 index 0000000..738bfdc --- /dev/null +++ b/src/meteole/forecast.py @@ -0,0 +1,547 @@ +import datetime as dt +import glob +import logging +import os +import shutil +import tempfile +from abc import abstractmethod +from pathlib import Path +from typing import Dict, List, Optional + +import pandas as pd +import xarray as xr +import xmltodict + +from meteole import const +from meteole.client import MeteoFranceClient +from meteole.errors import MissingDataError + +logger = logging.getLogger(__name__) + + +class Forecast(MeteoFranceClient): + """ + Provides a unified interface to query AROME and ARPEGE endpoints + + Attributes + ---------- + capabilities: pandas.DataFrame + coverage dataframe containing the details of all available coverage_ids + """ + + api_version = "1.0" + base_url = const.API_BASE_URL + + def __init__( + self, + api_key: str | None = None, + token: str | None = None, + territory: str = "FRANCE", + precision: float = 0.01, + application_id: str | None = None, + cache_dir: str | None = None, + ): + """Init the Forecast object.""" + super().__init__( + api_key=api_key, + application_id=application_id, + token=token, + ) + + if cache_dir is None: + initial_cache_dir = tempfile.TemporaryDirectory().name + else: + initial_cache_dir = cache_dir + + self.cache_dir = Path(initial_cache_dir) + + self.territory = territory # "FRANCE", "ANTIL", or others (see API doc) + self.precision = precision + self._validate_parameters() + + self.folderpath: Optional[Path] = None + self.get_capabilities() + + def get_capabilities(self) -> pd.DataFrame: + "Returns the coverage dataframe containing the details of all available coverage_ids" + + logger.info("Fetching all available coverages...") + + capabilities = self._get_capabilities() + df_capabilities = pd.DataFrame(capabilities["wcs:Capabilities"]["wcs:Contents"]["wcs:CoverageSummary"]) + df_capabilities = df_capabilities.rename( + columns={ + "wcs:CoverageId": "id", + "ows:Title": "title", + "wcs:CoverageSubtype": "subtype", + } + ) + df_capabilities["indicator"] = [coverage_id.split("___")[0] for coverage_id in df_capabilities["id"]] + df_capabilities["run"] = [ + coverage_id.split("___")[1].split("Z")[0] + "Z" for coverage_id in df_capabilities["id"] + ] + df_capabilities["interval"] = [ + coverage_id.split("___")[1].split("Z")[1].strip("_") for coverage_id in df_capabilities["id"] + ] + self.capabilities = df_capabilities + + nb_indicators = len(df_capabilities["indicator"].unique()) + nb_coverage_ids = df_capabilities.shape[0] + runs = df_capabilities["run"].unique() + + logger.info( + f"\n" + f"\t Successfully fetched {nb_coverage_ids} coverages,\n" + f"\t representing {nb_indicators} different indicators,\n" + f"\t across the last {len(runs)} runs (from {runs.min()} to {runs.max()}).\n" + f"\n" + f"\t Default run for `get_coverage`: {runs.max()})" + ) + + return df_capabilities + + @staticmethod + def _get_available_feature(grid_axis, feature_name): + features = [] + feature_grid_axis = [ + ax for ax in grid_axis if ax["gmlrgrid:GeneralGridAxis"]["gmlrgrid:gridAxesSpanned"] == feature_name + ] + if feature_grid_axis: + features = feature_grid_axis[0]["gmlrgrid:GeneralGridAxis"]["gmlrgrid:coefficients"].split(" ") + features = [int(feature) for feature in features] + return features + + def get_coverage_description(self, coverage_id: str) -> Dict: + """This endpoint returns the available axis (times, heights) to properly query coverage + + TODO: other informations can be fetched from this endpoint, not yet implemented. + + Args: + coverage_id (str): use :meth:`get_capabilities()` to list all available coverage_id + """ + + # get coverage description + description = self._get_coverage_description(coverage_id) + grid_axis = description["wcs:CoverageDescriptions"]["wcs:CoverageDescription"]["gml:domainSet"][ + "gmlrgrid:ReferenceableGridByVectors" + ]["gmlrgrid:generalGridAxis"] + + return { + "forecast_horizons": [ + int(time / 3600) for time in self.__class__._get_available_feature(grid_axis, "time") + ], + "heights": self.__class__._get_available_feature(grid_axis, "height"), + "pressures": self.__class__._get_available_feature(grid_axis, "pressure"), + } + + def get_coverages( + self, + coverage_ids: List[str], + lat: tuple = const.FRANCE_METRO_LATITUDES, + long: tuple = const.FRANCE_METRO_LONGITUDES, + ) -> pd.DataFrame: + """ + Convenient function to quickly fetch a list of indicators using defaults `heights` and `forecast_horizons` + + For finer control over heights and forecast_horizons use :meth:`get_coverage` + """ + coverages = [ + self.get_coverage( + coverage_id, + lat, + long, + ) + for coverage_id in coverage_ids + ] + + return pd.concat(coverages, axis=0) + + def _get_coverage_id( + self, + indicator: str, + run: Optional[str] = None, + interval: Optional[str] = None, + ) -> str: + """ + Get a coverage_id from `capabilities`. + + Parameters: + indicator (str): required. + run (Optional[str]): Identifies the model inference. Defaults to latest if None. Format "YYYY-MM-DDTHH:MM:SSZ". + interval (Optional[str]): aggregation period. Must be None for instant indicators, otherwise raises. Defaults to P1D for time-aggregated indicators like TOTAL_PRECIPITATION. + + Returns: + str: coverage_id + + Raises: + ValueError: If no or invalid 'indicator' + ValueError: If invalid interval, or if missing interval when required + """ + if not hasattr(self, "capabilities"): + self.get_capabilities() + + capabilities = self.capabilities[self.capabilities["indicator"] == indicator] + + if indicator not in self.indicators: + raise ValueError( + f"Unknown `indicator` - checkout `{self.model_name}.indicators` to have the full list." + ) + + if run is None: + run = capabilities.iloc[0]["run"] + logger.info(f"Using latest `run={run}`.") + + try: + dt.datetime.strptime(run, "%Y-%m-%dT%H.%M.%SZ") + except ValueError as exc: + raise ValueError(f"Run '{run}' is invalid. Expected format 'YYYY-MM-DDTHH.MM.SSZ'") from exc + + valid_runs = capabilities["run"].unique().tolist() + if run not in valid_runs: + raise ValueError(f"Run '{run}' is invalid. Valid runs : {valid_runs}") + + # handle interval + valid_intervals = capabilities["interval"].unique().tolist() + + if indicator in self.instant_indicators: + if interval is None: + # no interval is expected for instant indicators + pass + else: + raise ValueError( + f"interval={interval} is invalid. No interval is expected (=set to None) for instant indicator `{indicator}`." + ) + else: + if interval is None: + interval = "P1D" + logger.info( + f"`interval=None` is invalid for non-instant indicators. Using default `interval={interval}`" + ) + elif interval not in valid_intervals: + raise ValueError( + f"interval={interval} is invalid for non-instant indicators. `{indicator}`. Use valid intervals: {valid_intervals}" + ) + + coverage_id = f"{indicator}___{run}" + + if interval is not None: + coverage_id += f"_{interval}" + + return coverage_id + + def get_coverage( + self, + indicator: str | None = None, + lat: tuple = const.FRANCE_METRO_LATITUDES, + long: tuple = const.FRANCE_METRO_LONGITUDES, + heights: List[int] | None = None, + pressures: List[int] | None = None, + forecast_horizons: List[int] | None = None, + run: str | None = None, + interval: str | None = None, + coverage_id: str = "", + ) -> pd.DataFrame: + """Returns the data associated with the coverage_id for the selected parameters. + + Args: + coverage_id (str): coverage_id, get the list using :meth:`get_capabilities` + lat (tuple): minimum and maximum latitude + long (tuple): minimum and maximum longitude + heights (list): heights in meters + pressures (list): pressures in hPa + forecast_horizons (list): list of integers, representing the forecast horizon in hours + + Returns: + pd.DataFrame: The complete run for the specified execution. + """ + # ensure we only have one of coverage_id, indicator + if not bool(indicator) ^ bool(coverage_id): + raise ValueError("Argument `indicator` or `coverage_id` need to be set (only one of them)") + + if indicator: + coverage_id = self._get_coverage_id(indicator, run, interval) + + logger.debug(f"Using `coverage_id={coverage_id}`") + + axis = self.get_coverage_description(coverage_id) + + heights = self._raise_if_invalid_or_fetch_default("heights", heights, axis["heights"]) + pressures = self._raise_if_invalid_or_fetch_default("pressures", pressures, axis["pressures"]) + forecast_horizons = self._raise_if_invalid_or_fetch_default( + "forecast_horizons", forecast_horizons, axis["forecast_horizons"] + ) + + df_list = [ + self._get_data_single_forecast( + coverage_id=coverage_id, + height=height if height != -1 else None, + pressure=pressure if pressure != -1 else None, + forecast_horizon=forecast_horizon, + lat=lat, + long=long, + ) + for forecast_horizon in forecast_horizons + for pressure in pressures + for height in heights + ] + + return pd.concat(df_list, axis=0).reset_index(drop=True) + + def _raise_if_invalid_or_fetch_default( + self, param_name: str, inputs: List[int] | None, availables: List[int] + ) -> List[int]: + """ + Checks if the elements in `inputs` are in `availables` and raises a ValueError if not. + If `inputs` is empty or None, uses the first element from `availables` as the default value. + + Args: + param_name (str): The name of the parameter to validate. + inputs (Optional[List[int]]): The list of inputs to validate. + availables (List[int]): The list of available values. + + Returns: + List[int]: The validated list of inputs or the default value. + + Raises: + ValueError: If any of the inputs are not in `availables`. + """ + if inputs: + for input_value in inputs: + if input_value not in availables: + raise ValueError(f"`{param_name}={inputs}` is invalid. Available {param_name}: {availables}") + else: + inputs = availables[:1] or [-1] # using [-1] make sure we have an iterable. Using None makes things too complicated with mypy... + if inputs[0] != -1: + logger.info(f"Using `{param_name}={inputs}`") + return inputs + + @property + @abstractmethod + def model_name(self) -> str: + """AROME or ARPEGE""" + pass + + @property + @abstractmethod + def run_frequency(self) -> int: + """Frequency at which model is updated.""" + pass + + @property + @abstractmethod + def entry_point(self) -> str: + """Entry point to AROME/ARPEGE service.""" + pass + + @property + @abstractmethod + def indicators(self) -> str: + """The list of available indicators""" + pass + + @property + @abstractmethod + def instant_indicators(self) -> str: + """The list of instant indicators""" + pass + + def _get_capabilities(self) -> Dict: + """The Capabilities of the AROME/ARPEGE service.""" + + url = f"{self.base_url}/{self.entry_point}/GetCapabilities" + params = { + "service": "WCS", + "version": "2.0.1", + "language": "eng", + } + try: + response = self._get_request(url, params=params) + except MissingDataError as e: + logger.error(f"Error fetching the capabilities: {e}") + logger.error(f"URL: {url}") + logger.error(f"Params: {params}") + raise e + + xml = response.text + + try: + return xmltodict.parse(xml) + except MissingDataError as e: + logger.error(f"Error parsing the XML response: {e}") + logger.error(f"Response: {xml}") + raise e + + @abstractmethod + def _validate_parameters(self): + """Assert parameters are valid.""" + pass + + def _get_coverage_description(self, coverage_id: str) -> dict: + """Get the description of a coverage. + + .. warning:: + The return value is the raw XML data. + Not yet parsed to be usable. + In the future, it should be possible to use it to + get the available heights, times, latitudes and longitudes of the forecast. + + Parameters + ---------- + coverage_id: str + the Coverage ID. Use :meth:`get_coverage` to access the available coverage ids. + By default use the latest temperature coverage ID. + + Returns + ------- + description : dict + the description of the coverage. + """ + url = f"{self.base_url}/{self.entry_point}/DescribeCoverage" + params = { + "service": "WCS", + "version": "2.0.1", + "coverageid": coverage_id, + } + response = self._get_request(url, params=params) + return xmltodict.parse(response.text) + + def _transform_grib_to_df(self) -> pd.DataFrame: + "Transform grib file into pandas dataframe" + + ds = xr.open_dataset(self.filepath, engine="cfgrib") + df = ds.to_dataframe().reset_index() + os.remove(str(self.filepath)) + idx_files = glob.glob(f"{self.filepath}.*.idx") + for idx_file in idx_files: + os.remove(idx_file) + + return df + + def _get_data_single_forecast( + self, + coverage_id: str, + forecast_horizon: int, + pressure: int | None, + height: int | None, + lat: tuple, + long: tuple, + ) -> pd.DataFrame: + """Returns the forecasts for a given time and indicator. + + Args: + coverage_id (str): the indicator. + height (int): height in meters + pressure (int): pressure in hPa + forecast_horizon (int): the forecast horizon in hours (how many hours ahead) + lat (tuple): minimum and maximum latitude + long (tuple): minimum and maximum longitude + + Returns: + pd.DataFrame: The forecast for the specified time. + """ + + self._get_coverage_file( + coverage_id=coverage_id, + height=height, + pressure=pressure, + forecast_horizon_in_seconds=forecast_horizon * 3600, + lat=lat, + long=long, + ) + + df = self._transform_grib_to_df() + + df.drop(columns=["surface", "valid_time"], errors="ignore", inplace=True) + df.rename( + columns={ + "time": "run", + "heightAboveGround": "height", + "isobaricInhPa": "pressure", + "step": "forecast_horizon", + }, + inplace=True, + ) + + if self.folderpath is not None: + shutil.rmtree(self.folderpath) + + return df + + def _get_coverage_file( + self, + coverage_id: str, + height: int | None = None, + pressure: int | None = None, + forecast_horizon_in_seconds: int = 0, + lat: tuple = (37.5, 55.4), + long: tuple = (-12, 16), + file_format: str = "grib", + filepath: Path | None = None, + ) -> Path: + """Fetch the raster values of the model predictions. + + The raster is saved to a file in the cache directory. + + Parameters + ---------- + coverage_id: str + The ID of the coverage to fetch. Use the `get_coverage` method to find available coverage IDs. + By default, it uses the latest temperature coverage ID. + height: int, optional + The height in meters for the model. Defaults to 2 meters above ground. + The available heights can be accessed from the API, but this feature is not implemented yet. + forecast_horizon_in_seconds: int, optional + The forecast horizon in seconds into the future. Defaults to 0s (current time). + The available forecast horizons can be known via :meth:`get_coverage_description` + lat: tuple[float], optional + The minimum and maximum latitudes to return. Defaults to the latitudes of France. + long: tuple[float], optional + The minimum and maximum longitudes to return. Defaults to the longitudes of France. + file_format: str, optional + The format of the file to save the raster data. Defaults to "grib". + filepath: Path, optional + The path where the file will be saved. If not provided, it will be saved in the cache directory. + + Returns + ------- + filename : pathlib.Path + The path to the file containing the raster data in the specified format. + + .. see-also:: + :func:`.raster.plot_tiff_file` to plot the file. + """ + self.filepath = filepath + + file_extension = "tiff" if file_format == "tiff" else "grib" + + filename = ( + f"{height or '_'}m_{forecast_horizon_in_seconds}Z_{lat[0]}-{lat[1]}_{long[0]}-{long[1]}.{file_extension}" + ) + + if self.filepath is None: + current_working_directory = Path(os.getcwd()) + self.filepath = current_working_directory / coverage_id / filename + self.folderpath = current_working_directory / coverage_id + logger.debug(f"{self.filepath}") + logger.debug("File not found in Cache, fetching data") + url = f"{self.base_url}/{self.entry_point}/GetCoverage" + params = { + "service": "WCS", + "version": "2.0.1", + "coverageid": coverage_id, + "format": "application/wmo-grib", + "subset": [ + *([f"pressure({pressure})"] if pressure is not None else []), + *([f"height({height})"] if height is not None else []), + f"time({forecast_horizon_in_seconds})", + f"lat({lat[0]},{lat[1]})", + f"long({long[0]},{long[1]})", + ], + } + response = self._get_request(url, params=params) + + self.filepath.parent.mkdir(parents=True, exist_ok=True) + with open(self.filepath, "wb") as f: + f.write(response.content) + + return self.filepath diff --git a/src/meteole/raster.py b/src/meteole/raster.py new file mode 100644 index 0000000..0c8733d --- /dev/null +++ b/src/meteole/raster.py @@ -0,0 +1,67 @@ +"""Functionnalities to deal with the raster data""" +from typing import Optional + +import cartopy.crs as ccrs +import matplotlib.pyplot as plt +import rasterio +from cartopy import feature + + +def open_tiff_file(filename): + """Ope, a tiff file.""" + with rasterio.open(filename) as source: + field = source.read(1, masked=True) + transform = source.transform + return field, transform + + +def plot_tiff_file( + filename, + data_type: str, + unit: str, + title: Optional[str] = None, +): + """ + Plot a tiff file. + + Parameters + ---------- + filename : file + filename from a get coverage + data_type: str + datatype of the coverage : temperature,wind,humidity ... + unit: str + Unit of the coverage (°C, Kmh) + title: str, optional + Plot title + + .. note:: + This Function is more an "How-To" rather than a tool to use as is. + + """ + data_field, transform = open_tiff_file(filename=filename) + fig = plt.figure(figsize=(10, 10)) + ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree()) + im = ax.imshow( + data_field, + cmap="jet", + extent=[ + transform[2], + transform[2] + transform[0] * data_field.shape[1], + transform[5] + transform[4] * data_field.shape[0], + transform[5], + ], + ) + ax.add_feature(feature.BORDERS.with_scale("10m"), color="black", linewidth=1) + ax.add_feature(feature.COASTLINE.with_scale("10m"), color="black", linewidth=1) + cbar = plt.colorbar(im, ax=ax, shrink=0.5) + height = filename.stem.split("_")[0] + computed_at = filename.parent.stem.split("_")[-1] + + cbar.set_label(f"{data_type}_{unit}") + + if title is None: + ax.set_title(f"{data_type} at {height} above ground \n computed at {computed_at} ") + else: + ax.set_title(f"{title}_{computed_at}") + return ax diff --git a/src/meteole/vigilance.py b/src/meteole/vigilance.py new file mode 100644 index 0000000..f3ccbff --- /dev/null +++ b/src/meteole/vigilance.py @@ -0,0 +1,145 @@ +import logging +from io import BytesIO +from typing import Tuple + +import matplotlib.image as mpimg +import matplotlib.pyplot as plt +import pandas as pd + +from meteole import client, const +from meteole.errors import MissingDataError + +logger = logging.getLogger(__name__) + + +class Vigilance(client.MeteoFranceClient): + """Wrapper around the meteo-France API for the vigilance data. + Ressources are: + - textesvigilance + - cartevigilance + + Documentation + ------------- + See: + - https://portail-api.meteofrance.fr/web/fr/api/DonneesPubliquesVigilance + - https://donneespubliques.meteofrance.fr/client/document/descriptiftechnique_vigilancemetropole_donneespubliques_v4_20230911_307.pdf + """ + + base_url = const.API_BASE_URL + "DPVigilance/" + version = "v1" + + def __init__( + self, + api_key: str | None = None, + token: str | None = None, + application_id: str | None = None, + ): + """ + Init the Vigilance object. + Parameters + ---------- + api_key : str | None, optional + The API Key, by default None + token : str | None, optional + The API Token, by default None + application_id : str | None, optional + The Application ID, by default None + Note + ---- + See :class:`.MeteoFranceClient` for the parameters `api_key`, `token` and `application_id`. + """ + + super().__init__(api_key, token, application_id) + + def get_textes_vigilance(self) -> dict: + """ + Get bulletin de vigilance + + Returns: + -------- + dict: a Dict with bulletin de vigilance + """ + + url = self.base_url + self.version + "/textesvigilance/encours" + logger.debug(f"GET {url}") + try: + req = self._get_request(url) + return req.json() + except MissingDataError as e: + if "no matching blob" in e.message: + logger.warning("La vigilance en cours ne nécessite pas de publication") + else: + logger.error(f"Unexpected error: {e}") + return {} + except Exception as e: + logger.error(f"Unexpected error: {e}") + return {} + + def get_carte_vigilance(self) -> dict: + """ + Get "carte" de Vigilance with risk prediction + + Returns: + -------- + dict: a Dict with risk prediction + """ + url = self.base_url + self.version + "/cartevigilance/encours" + logger.debug(f"GET {url}") + req = self._get_request(url) + + return req.json() + + def get_phenomenon(self) -> Tuple[pd.DataFrame, pd.DataFrame]: + """ + get risk prediction by phenomenon and by domain + Returns: + -------- + pd.DataFrame: a DataFrame with phenomenon by id + pd.DataFrame: a DataFrame with phenomenon by domain + """ + df_carte = pd.DataFrame(self.get_carte_vigilance()) + periods_data = df_carte.loc["periods", "product"] + df_periods = pd.json_normalize(periods_data) + + df_j = df_periods[df_periods["echeance"] == "J"] + df_j1 = df_periods[df_periods["echeance"] == "J1"] + + df_phenomenon_j = pd.json_normalize(df_j["per_phenomenon_items"].explode()) + df_phenomenon_j1 = pd.json_normalize(df_j1["per_phenomenon_items"].explode()) + df_phenomenon_j["echeance"] = "J" + df_phenomenon_j1["echeance"] = "J1" + + df_phenomenon = pd.concat([df_phenomenon_j, df_phenomenon_j1]).reset_index(drop=True) + df_phenomenon["phenomenon_libelle"] = df_phenomenon["phenomenon_id"].map(const.dict_phenomenon_id) + + df_timelaps_j = pd.json_normalize(df_j["timelaps.domain_ids"].explode()) + df_timelaps_j1 = pd.json_normalize(df_j1["timelaps.domain_ids"].explode()) + df_timelaps_j["echeance"] = "J" + df_timelaps_j1["echeance"] = "J1" + + df_timelaps = pd.concat([df_timelaps_j, df_timelaps_j1]).reset_index(drop=True) + + return df_phenomenon, df_timelaps + + def get_vignette(self) -> None: + """ + Get png + """ + url = self.base_url + self.version + "/vignettenationale-J-et-J1/encours" + + logger.debug(f"GET {url}") + req = self._get_request(url) + + if req.status_code == 200: + filename = req.headers.get("content-disposition").split("filename=")[1] + filename = filename.strip('"') + + with open(filename, "wb") as f: + f.write(req.content) + + img = mpimg.imread(BytesIO(req.content), format="png") + plt.imshow(img) + plt.axis("off") + plt.show() + else: + req.raise_for_status() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..acea1cc --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,111 @@ +import pytest +from unittest.mock import patch, MagicMock, Mock + +from meteole.client import MeteoFranceClient + +def test_init_with_api_key(): + api = MeteoFranceClient(api_key="dummy_api_key") + assert api.api_key == "dummy_api_key" + assert api.token is None + assert api.application_id is None + +def test_init_with_token(): + api = MeteoFranceClient(token="dummy_token") + assert api.api_key is None + assert api.token == "dummy_token" + assert api.application_id is None + +@patch('meteole.client.MeteoFranceClient.connect') +def test_init_with_application_id(mock_connect): + api = MeteoFranceClient(application_id="dummy_app_id") + assert api.application_id == "dummy_app_id" + assert api.api_key is None + +def test_connect_no_credentials(): + with pytest.raises(ValueError): + MeteoFranceClient() + +@patch("requests.post") +def test_get_token(mock_post): + mock_post.return_value.json.return_value = {"access_token": "dummy_token"} + + api = MeteoFranceClient(application_id="dummy_app_id") + token = api.get_token() + + assert token == "dummy_token" + assert api.token == "dummy_token" + +@patch("requests.Session.get") +@patch.object(MeteoFranceClient, "get_token") +def test_get_request_success(mock_get_token, mock_get): + api = MeteoFranceClient(api_key="dummy_api_key") + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.json.return_value = {"data": "some data"} + mock_get.return_value = mock_response + + response = api._get_request("https://dummyurl.com") + assert response.status_code == 200 + assert response.json() == {"data": "some data"} + +@patch("requests.Session.get") +@patch.object(MeteoFranceClient, "get_token") +def test_get_request_token_expired(mock_get_token, mock_get): + api = MeteoFranceClient(api_key="dummy_api_key") + + expired_response = MagicMock() + expired_response.status_code = 401 + expired_response.headers = {"Content-Type": "application/json"} + expired_response.json.return_value = {"description": "Invalid JWT token"} + + valid_response = MagicMock() + valid_response.status_code = 200 + valid_response.json.return_value = {"data": "some data"} + + mock_get.side_effect = [expired_response, valid_response, valid_response] + + response = api._get_request("https://dummyurl.com") + assert response.status_code == 200 + assert response.json() == {"data": "some data"} + + response = api._get_request("https://dummyurl.com") + assert response.status_code == 200 + assert response.json() == {"data": "some data"} + +def test_token_expired(): + api = MeteoFranceClient(api_key="dummy_api_key") + expired_response = MagicMock() + expired_response.status_code = 401 + expired_response.headers = {"Content-Type": "application/json"} + expired_response.json = lambda: {"description": "Invalid JWT token"} + + assert api._token_expired(expired_response) == True + +def test_token_not_expired(): + api = MeteoFranceClient(api_key="dummy_api_key") + valid_response = MagicMock() + valid_response.status_code = 200 + valid_response.headers = {"Content-Type": "application/json"} + valid_response.text = lambda: {"description": "Valid JWT token"} + + assert api._token_expired(valid_response) == False + +@patch("requests.Session.get") +@patch.object(MeteoFranceClient, "get_token") +def test_get_request_specific_error(mock_get_token, mock_get): + api = MeteoFranceClient(api_key="dummy_api_key") + + error_response = MagicMock() + error_response.status_code = 502 + error_response.json.return_value = {"error": "Bad Gateway"} + + valid_response = MagicMock() + valid_response.status_code = 200 + valid_response.json.return_value = {"data": "some data"} + + mock_get.side_effect = [error_response, valid_response] + + response = api._get_request("https://dummyurl.com") + assert response.status_code == 200 + assert response.json() == {"data": "some data"} + assert mock_get.call_count == 2 \ No newline at end of file diff --git a/tests/test_forecast.py b/tests/test_forecast.py new file mode 100644 index 0000000..7585f85 --- /dev/null +++ b/tests/test_forecast.py @@ -0,0 +1,368 @@ +import pytest +import unittest +from unittest.mock import patch, MagicMock +from pathlib import Path +import tempfile +from meteole import const +from meteole.arome import AromeForecast +from meteole.arpege import ArpegeForecast, RELATION_TERRITORY_TO_PREC_ARPEGE +import pandas as pd + + +class TestAromeForecast(unittest.TestCase): + + def setUp(self): + self.precision = 0.01 + self.territory = "FRANCE" + self.api_key = "fake_api_key" + self.token = "fake_token" + self.application_id = "fake_app_id" + self.cache_dir = tempfile.mkdtemp(prefix="test_cache_") + + @patch("meteole.arome.AromeForecast.get_capabilities") + def test_initialization(self, mock_get_capabilities): + mock_get_capabilities.return_value = None + + forecast = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) + + self.assertEqual(forecast.precision, self.precision) + self.assertEqual(forecast.territory, self.territory) + self.assertEqual(forecast.api_key, self.api_key) + self.assertEqual(forecast.token, self.token) + self.assertEqual(forecast.application_id, self.application_id) + self.assertEqual(forecast.cache_dir, Path(self.cache_dir)) + self.assertEqual(forecast.model_name, "arome") + self.assertEqual(forecast.run_frequency, 3) + self.assertEqual(len(forecast.indicators), 19) + mock_get_capabilities.assert_called_once() + + def test_invalid_precision(self): + with self.assertRaises(ValueError): + AromeForecast(precision=0.1) + + def test_invalid_territory(self): + with self.assertRaises(ValueError): + AromeForecast(territory="INVALID") + + @patch("meteole.arome.AromeForecast._get_request") + def test_get_capabilities(self, mock_get_request): + mock_response = MagicMock() + mock_response.text = """ + + + + GEOMETRIC_HEIGHT__GROUND_OR_WATER_SURFACE___2024-10-31T00.00.00Z + Geometric height + ReferenceableGridCoverage + + + SHORT_WAVE_RADIATION_FLUX__GROUND_OR_WATER_SURFACE___2024-11-01T18.00.00Z_P2D + short-wave radiation flux + ReferenceableGridCoverage + + + + """ + mock_get_request.return_value = mock_response + + arome = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) + # arome.get_capabilities() is made during init + # this means arome.capabilities exists now + + self.assertEqual(list(arome.capabilities['id']), [ + "GEOMETRIC_HEIGHT__GROUND_OR_WATER_SURFACE___2024-10-31T00.00.00Z", + "SHORT_WAVE_RADIATION_FLUX__GROUND_OR_WATER_SURFACE___2024-11-01T18.00.00Z_P2D" + ]) + + + @patch("meteole.arome.AromeForecast._get_request") + def test_get_coverage_id(self, mock_get_request): + mock_response = MagicMock() + mock_response.text = """ + + + + GEOMETRIC_HEIGHT__GROUND_OR_WATER_SURFACE___2024-10-31T00.00.00Z + Geometric height + ReferenceableGridCoverage + + + TOTAL_WATER_PRECIPITATION__GROUND_OR_WATER_SURFACE___2024-11-01T18.00.00Z_P2D + short-wave radiation flux + ReferenceableGridCoverage + + + + """ + mock_get_request.return_value = mock_response + + arome = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) # arome.capabilities is set up + + run = "2024-11-01T18.00.00Z" + interval = "P2D" + + # test wrong indicator raises + indicator = "wrong_indicator" + + with pytest.raises(ValueError): + coverage_id = arome._get_coverage_id(indicator, run, interval) + + indicator = "TOTAL_WATER_PRECIPITATION__GROUND_OR_WATER_SURFACE" + coverage_id = arome._get_coverage_id(indicator, run, interval) + assert coverage_id == "TOTAL_WATER_PRECIPITATION__GROUND_OR_WATER_SURFACE___2024-11-01T18.00.00Z_P2D" + + @patch("meteole.arome.AromeForecast.get_capabilities") + @patch("meteole.arome.AromeForecast._get_request") + def test_get_coverage_description(self, mock_get_request, mock_get_capabilities): + mock_response = MagicMock() + mock_response.text = """ + + + U_COMPONENT_OF_WIND_GUST__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND___2024-06-20T09.00.00Z + + ReferenceableGridCoverage + application/wmo-grib + + + + """ + mock_get_request.return_value = mock_response + mock_get_capabilities.return_value = None + + forecast = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) + + description = forecast._get_coverage_description("coverage_1") + self.assertIn("wcs:CoverageDescriptions", description) + + @patch("meteole.arome.AromeForecast.get_capabilities") + @patch("meteole.arome.AromeForecast._get_request") + def test_get_coverage_file(self, mock_get_request, mock_get_capabilities): + mock_response = MagicMock() + mock_response.content = b"fake_data" + mock_get_request.return_value = mock_response + mock_get_capabilities.return_value = None + + forecast = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) + + coverage_id = "coverage_1" + forecast._get_coverage_file( + coverage_id=coverage_id, + height=2, + forecast_horizon_in_seconds=0, + lat=(37.5, 55.4), + long=(-12, 16), + ) + + import os + expected_path = Path(os.getcwd()) / coverage_id / "2m_0Z_37.5-55.4_-12-16.grib" + self.assertTrue(expected_path.exists()) + + expected_path.unlink() + + @patch("meteole.arome.AromeForecast.get_capabilities") + @patch("meteole.arome.AromeForecast._transform_grib_to_df") + @patch("meteole.arome.AromeForecast._get_coverage_file") + def test_get_data_single_forecast(self, mock_get_coverage_file, mock_transform_grib_to_df, mock_get_capabilities): + mock_transform_grib_to_df.return_value = pd.DataFrame({"data": [1, 2, 3], "heightAboveGround": ["1", "2", "3"]}) + + forecast = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) + + df = forecast._get_data_single_forecast( + coverage_id="coverage_1", + height=2, + pressure=None, + forecast_horizon=0, + lat=(37.5, 55.4), + long=(-12, 16), + ) + + self.assertTrue("data" in df.columns) + + + @patch("meteole.arome.AromeForecast.get_coverage_description") + @patch("meteole.arome.AromeForecast.get_capabilities") + @patch("meteole.arome.AromeForecast._get_data_single_forecast") + def test_get_coverage(self, mock_get_data_single_forecast, mock_get_capabilities, mock_get_coverage_description): + mock_get_data_single_forecast.return_value = pd.DataFrame({ + "latitude": [1, 2, 3], + "longitude": [4, 5, 6], + "time": [7, 8, 9], + "step": [10, 11, 12], + "valid_time": [16, 17, 18], + "data": [19, 20, 21] # this column name varies depending on the coverage_id + }) + mock_get_coverage_description.return_value = { + "heights": [2], + "forecast_horizons": [0], + "pressures": [] + } + + forecast = AromeForecast( + precision=self.precision, + territory=self.territory, + api_key=self.api_key, + token=self.token, + application_id=self.application_id, + cache_dir=self.cache_dir, + ) + + forecast.get_coverage( + coverage_id="toto", + heights=[2], + forecast_horizons=[0], + lat=(37.5, 55.4), + long=(-12, 16), + ) + + mock_get_data_single_forecast.assert_called_once_with( + coverage_id="toto", + height=2, + pressure=None, + forecast_horizon=0, + lat=(37.5, 55.4), + long=(-12, 16) + ) + + +class TestArpegeForecast(unittest.TestCase): + + def setUp(self): + self.territory = "EUROPE" + self.api_key = "fake_api_key" + self.token = "fake_token" + self.application_id = "fake_app_id" + + @patch("meteole.arpege.ArpegeForecast.get_capabilities") + def test_initialization(self, mock_get_capabilities): + territory = "EUROPE" + api_key = "test_api_key" + token = "test_token" + application_id = "test_app_id" + + arpege_forecast = ArpegeForecast( + territory=territory, + api_key=api_key, + token=token, + application_id=application_id, + cache_dir='toto' + ) + + self.assertEqual(arpege_forecast.territory, territory) + self.assertEqual(arpege_forecast.precision, RELATION_TERRITORY_TO_PREC_ARPEGE[territory]) + self.assertEqual(arpege_forecast.cache_dir, Path('toto')) + self.assertEqual(arpege_forecast.api_key, api_key) + self.assertEqual(arpege_forecast.token, token) + self.assertEqual(arpege_forecast.application_id, application_id) + self.assertEqual(arpege_forecast.model_name, "arpege") + self.assertEqual(arpege_forecast.run_frequency, 6) + self.assertEqual(len(arpege_forecast.indicators), 47) + mock_get_capabilities.assert_called_once() + + + @patch("meteole.arpege.ArpegeForecast.get_capabilities") + def test_validate_parameters(self, mock_get_capabilities): + valid_territory = "EUROPE" + invalid_territory = "INVALID_TERRITORY" + + # Test with a valid territory + arpege_forecast = ArpegeForecast(territory=valid_territory, api_key='toto') + + try: + arpege_forecast._validate_parameters() + except ValueError: + self.fail("_validate_parameters raised ValueError unexpectedly with valid territory!") + + # Test with an invalid territory + arpege_forecast.territory = invalid_territory + with self.assertRaises(ValueError): + arpege_forecast._validate_parameters() + + @patch("meteole.arpege.ArpegeForecast.get_capabilities") + @patch('meteole.client.MeteoFranceClient.connect') + def test_entry_point(self, mock_MeteoFranceClient_connect, mock_get_capabilities): + territory = "EUROPE" + arpege_forecast = ArpegeForecast(territory=territory) + expected_entry_point = f"wcs/MF-NWP-GLOBAL-ARPEGE-{const.PRECISION_FLOAT_TO_STR[RELATION_TERRITORY_TO_PREC_ARPEGE[territory]]}-{territory}-WCS" + self.assertEqual(arpege_forecast.entry_point, expected_entry_point) + +class TestGetAvailableFeature(unittest.TestCase): + def setUp(self): + self.grid_axis = [ + { + "gmlrgrid:GeneralGridAxis": { + "gmlrgrid:gridAxesSpanned": "time", + "gmlrgrid:coefficients": "3600 7200 10800" + } + }, + { + "gmlrgrid:GeneralGridAxis": { + "gmlrgrid:gridAxesSpanned": "height", + "gmlrgrid:coefficients": "100 200 300" + } + }, + { + "gmlrgrid:GeneralGridAxis": { + "gmlrgrid:gridAxesSpanned": "pressure", + "gmlrgrid:coefficients": "1000 2000 3000" + } + } + ] + + def test_get_available_feature_time(self): + result = AromeForecast._get_available_feature(self.grid_axis, "time") + self.assertEqual(result, [3600, 7200, 10800]) + + def test_get_available_feature_height(self): + result = AromeForecast._get_available_feature(self.grid_axis, "height") + self.assertEqual(result, [100, 200, 300]) + + def test_get_available_feature_pressure(self): + result = AromeForecast._get_available_feature(self.grid_axis, "pressure") + self.assertEqual(result, [1000, 2000, 3000]) + + def test_get_available_feature_not_found(self): + result = AromeForecast._get_available_feature(self.grid_axis, "nonexistent") + self.assertEqual(result, []) + diff --git a/tests/test_vigilance.py b/tests/test_vigilance.py new file mode 100644 index 0000000..7ea103b --- /dev/null +++ b/tests/test_vigilance.py @@ -0,0 +1,69 @@ +import unittest +from unittest.mock import patch, MagicMock +from meteole.vigilance import Vigilance +import pandas as pd + +class TestVigilance(unittest.TestCase): + + def setUp(self): + self.api_key = "fake_api_key" + self.token = "fake_token" + self.application_id = "fake_app_id" + self.vigilance = Vigilance(api_key=self.api_key, token=self.token, application_id=self.application_id) + + @patch("meteole.vigilance.Vigilance._get_request") + def test_get_textes_vigilance(self, mock_get_request): + mock_response = MagicMock() + mock_response.json.return_value = {"data": "some_data"} + mock_get_request.return_value = mock_response + + result = self.vigilance.get_textes_vigilance() + self.assertEqual(result, {"data": "some_data"}) + mock_get_request.assert_called_once_with("https://public-api.meteofrance.fr/public/DPVigilance/v1/textesvigilance/encours") + + @patch("meteole.vigilance.Vigilance._get_request") + def test_get_carte_vigilance(self, mock_get_request): + mock_response = MagicMock() + mock_response.json.return_value = {"data": "some_data"} + mock_get_request.return_value = mock_response + + result = self.vigilance.get_carte_vigilance() + self.assertEqual(result, {"data": "some_data"}) + mock_get_request.assert_called_once_with("https://public-api.meteofrance.fr/public/DPVigilance/v1/cartevigilance/encours") + + @patch("meteole.vigilance.Vigilance.get_carte_vigilance") + def test_get_phenomenon(self, mock_get_carte_vigilance): + mock_get_carte_vigilance.return_value = { + "product": { + "periods": [ + {"echeance": "J", + "per_phenomenon_items": [{"phenomenon_id": '1', "any_color_count": 5, "phenomenon_counts": [{"color_id": 2, "color_name": "Jaune", "count": 3}]}], + "timelaps.domain_ids": [{"domain_id": 1, 'max_color_id': 1}]}, + {"echeance": "J1", + "per_phenomenon_items": [{"phenomenon_id": '2', "any_color_count": 5, "phenomenon_counts": [{"color_id": 2, "color_name": "Jaune", "count": 3}]}], + "timelaps.domain_ids": [{"domain_id": 2, 'max_color_id': 2}]} + ] + } + } + + with patch.object(self.vigilance, 'get_carte_vigilance', return_value=mock_get_carte_vigilance.return_value): + df_phenomenon, df_timelaps = self.vigilance.get_phenomenon() + + expected_phenomenon_data = { + "phenomenon_id": ['1', '2'], + "any_color_count": [5, 5], + "phenomenon_counts": [[{"color_id": 2, "color_name": "Jaune", "count": 3}], [{"color_id": 2, "color_name": "Jaune", "count": 3}]], + "echeance": ["J", "J1"], + "phenomenon_libelle": ["vent", "pluie"] + } + expected_phenomenon_df = pd.DataFrame(expected_phenomenon_data) + + expected_timelaps_data = { + "domain_id": [1, 2], + "max_color_id": [1, 2], + "echeance": ["J", "J1"], + } + expected_timelaps_df = pd.DataFrame(expected_timelaps_data) + + pd.testing.assert_frame_equal(df_phenomenon, expected_phenomenon_df) + pd.testing.assert_frame_equal(df_timelaps, expected_timelaps_df) \ No newline at end of file diff --git a/tutorial/access_viligance_bulletin.ipynb b/tutorial/access_viligance_bulletin.ipynb new file mode 100644 index 0000000..4e3c222 --- /dev/null +++ b/tutorial/access_viligance_bulletin.ipynb @@ -0,0 +1,644 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Vigilance Bulletin\n", + "\n", + "This tutorial will help you access the vigilance bulletin\n", + "\n", + "For more documentation, click [here](https://donneespubliques.meteofrance.fr/?fond=produit&id_produit=305&id_rubrique=50).\n", + "\n", + "Contents:\n", + "\n", + "- Init Vigilance Class\n", + "- Access Data" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import pandas as pd\n", + "from meteole import Vigilance" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Init Vigilance Class" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**Requirements notice** : TODO Link to the documentation to have application_id" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# load application_id from .env\n", + "from dotenv import load_dotenv\n", + "load_dotenv()\n", + "application_id = os.getenv(\"APPLICATION_ID\", None)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "client = Vigilance(application_id = application_id)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Collect Forecasted phenomenon\n", + "\n", + "Collect vigilance data from Météo France, including the forecasted phenomenon in df_phenomenon and the maximum intensity for each zone in df_timelaps" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "df_phenomenon, df_timelaps = client.get_phenomenon()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
phenomenon_idany_color_countphenomenon_countsecheancephenomenon_libelle
0162[{'color_id': 2, 'color_name': 'Jaune', 'count...Jvent
1416[{'color_id': 2, 'color_name': 'Jaune', 'count...Jcrues
2216[{'color_id': 2, 'color_name': 'Jaune', 'count...Jpluie
3568[{'color_id': 2, 'color_name': 'Jaune', 'count...Jneige / verglas
496[{'color_id': 2, 'color_name': 'Jaune', 'count...Jvagues submersion
\n", + "
" + ], + "text/plain": [ + " phenomenon_id any_color_count \\\n", + "0 1 62 \n", + "1 4 16 \n", + "2 2 16 \n", + "3 5 68 \n", + "4 9 6 \n", + "\n", + " phenomenon_counts echeance \\\n", + "0 [{'color_id': 2, 'color_name': 'Jaune', 'count... J \n", + "1 [{'color_id': 2, 'color_name': 'Jaune', 'count... J \n", + "2 [{'color_id': 2, 'color_name': 'Jaune', 'count... J \n", + "3 [{'color_id': 2, 'color_name': 'Jaune', 'count... J \n", + "4 [{'color_id': 2, 'color_name': 'Jaune', 'count... J \n", + "\n", + " phenomenon_libelle \n", + "0 vent \n", + "1 crues \n", + "2 pluie \n", + "3 neige / verglas \n", + "4 vagues submersion " + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_phenomenon.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
domain_idmax_color_idphenomenon_itemsecheance
0103[{'phenomenon_id': '1', 'phenomenon_max_color_...J
1112[{'phenomenon_id': '1', 'phenomenon_max_color_...J
2122[{'phenomenon_id': '1', 'phenomenon_max_color_...J
3131[{'phenomenon_id': '1', 'phenomenon_max_color_...J
4143[{'phenomenon_id': '1', 'phenomenon_max_color_...J
\n", + "
" + ], + "text/plain": [ + " domain_id max_color_id phenomenon_items \\\n", + "0 10 3 [{'phenomenon_id': '1', 'phenomenon_max_color_... \n", + "1 11 2 [{'phenomenon_id': '1', 'phenomenon_max_color_... \n", + "2 12 2 [{'phenomenon_id': '1', 'phenomenon_max_color_... \n", + "3 13 1 [{'phenomenon_id': '1', 'phenomenon_max_color_... \n", + "4 14 3 [{'phenomenon_id': '1', 'phenomenon_max_color_... \n", + "\n", + " echeance \n", + "0 J \n", + "1 J \n", + "2 J \n", + "3 J \n", + "4 J " + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_timelaps.head()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Collect text of monitoring bulletins\n", + "\n", + "Contains the text of monitoring bulletins, whether national, zonal (in the sense of defense zones) or departmental. It is issued in addition to the Vigilance card, when the meteorological situation so requires (systematically in Vigilance Orange and Red, when necessary in Vigilance Yellow)." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "textes_vigilance = client.get_textes_vigilance()" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [], + "source": [ + "test_texte = pd.json_normalize(textes_vigilance)" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
product.warning_typeproduct.type_cdpproduct.version_vigilanceproduct.version_cdpproduct.domain_idproduct.domain_nameproduct.update_timeproduct.text_bloc_itemsmeta.snapshot_idmeta.product_datetimemeta.generation_timestamp
0vigilancecdp_textesV61.0.0FRAFrance2024-11-21T07:05:38Z[{'domain_id': 'FRA', 'domain_name': 'France',...a594306a-1134-4584-b2b8-4bccbbd934db2024-11-21T06:30:00+00:002024-11-21T07:05:38+00:00
\n", + "
" + ], + "text/plain": [ + " product.warning_type product.type_cdp product.version_vigilance \\\n", + "0 vigilance cdp_textes V6 \n", + "\n", + " product.version_cdp product.domain_id product.domain_name \\\n", + "0 1.0.0 FRA France \n", + "\n", + " product.update_time product.text_bloc_items \\\n", + "0 2024-11-21T07:05:38Z [{'domain_id': 'FRA', 'domain_name': 'France',... \n", + "\n", + " meta.snapshot_id meta.product_datetime \\\n", + "0 a594306a-1134-4584-b2b8-4bccbbd934db 2024-11-21T06:30:00+00:00 \n", + "\n", + " meta.generation_timestamp \n", + "0 2024-11-21T07:05:38+00:00 " + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "test_texte.head()" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "df_text_bloc_items = pd.json_normalize(test_texte['product.text_bloc_items'].explode())" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
domain_iddomain_namebloc_titlebloc_idbloc_items
0FRAFranceBulletin de Vigilance météo nationalBULLETIN_NATIONAL[{'id': 'NAT_SITUATION_PAYS', 'type_name': 'Si...
1ZDF_ESTDéfense EstBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S...
2ZDF_PARISDéfense ParisBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S...
3ZDF_NORDDéfense NordBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_INCERTITUDE_ZONAL', 'type_name': ...
4ZDF_SUD_ESTDéfense Sud-EstBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S...
5ZDF_SUD_OUESTDéfense Sud-OuestBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S...
6ZDF_SUDDéfense SudBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S...
7ZDF_OUESTDéfense OuestBulletin de Vigilance météo zonalBULLETIN_ZONAL[{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S...
810AubeBulletin de Vigilance météo Zone Est*BULLETIN_DEPARTEMENTAL[{'id': 'DEP_SITUATION_ZONAL', 'type_name': 'S...
911AudeBulletin de Vigilance météo Arc méditerranéen ...BULLETIN_DEPARTEMENTAL[{'id': 'DEP_SITUATION_ZONAL', 'type_name': 'S...
\n", + "
" + ], + "text/plain": [ + " domain_id domain_name \\\n", + "0 FRA France \n", + "1 ZDF_EST Défense Est \n", + "2 ZDF_PARIS Défense Paris \n", + "3 ZDF_NORD Défense Nord \n", + "4 ZDF_SUD_EST Défense Sud-Est \n", + "5 ZDF_SUD_OUEST Défense Sud-Ouest \n", + "6 ZDF_SUD Défense Sud \n", + "7 ZDF_OUEST Défense Ouest \n", + "8 10 Aube \n", + "9 11 Aude \n", + "\n", + " bloc_title bloc_id \\\n", + "0 Bulletin de Vigilance météo national BULLETIN_NATIONAL \n", + "1 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "2 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "3 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "4 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "5 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "6 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "7 Bulletin de Vigilance météo zonal BULLETIN_ZONAL \n", + "8 Bulletin de Vigilance météo Zone Est* BULLETIN_DEPARTEMENTAL \n", + "9 Bulletin de Vigilance météo Arc méditerranéen ... BULLETIN_DEPARTEMENTAL \n", + "\n", + " bloc_items \n", + "0 [{'id': 'NAT_SITUATION_PAYS', 'type_name': 'Si... \n", + "1 [{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S... \n", + "2 [{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S... \n", + "3 [{'id': 'ZON_INCERTITUDE_ZONAL', 'type_name': ... \n", + "4 [{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S... \n", + "5 [{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S... \n", + "6 [{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S... \n", + "7 [{'id': 'ZON_SITUATION_ZONAL', 'type_name': 'S... \n", + "8 [{'id': 'DEP_SITUATION_ZONAL', 'type_name': 'S... \n", + "9 [{'id': 'DEP_SITUATION_ZONAL', 'type_name': 'S... " + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "df_text_bloc_items.head(10)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Map display" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "client.get_vignette()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "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.18" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/tutorial/arome.ipynb b/tutorial/arome.ipynb new file mode 100644 index 0000000..65cdaf5 --- /dev/null +++ b/tutorial/arome.ipynb @@ -0,0 +1,267 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# AROME" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import random\n", + "import dotenv\n", + "from meteole import AromeForecast\n", + "\n", + "dotenv.load_dotenv()\n", + "\n", + "application_id = os.getenv(\"APPLICATION_ID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Fetching all available coverages...\n", + "\n", + "\t Successfully fetched 2020 coverages,\n", + "\t representing 19 different indicators,\n", + "\t across the last 37 runs (from 2024-12-08T00.00.00Z to 2024-12-12T12.00.00Z).\n", + "\n", + "\t Default run for `get_coverage`: 2024-12-12T12.00.00Z)\n" + ] + } + ], + "source": [ + "# init client\n", + "arome = AromeForecast(application_id=application_id)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Indicator: TOTAL_PRECIPITATION_RATE__GROUND_OR_WATER_SURFACE\n" + ] + } + ], + "source": [ + "# pick a random indicator\n", + "\n", + "random_indicator = random.choice(arome.indicators)\n", + "print(f\"Indicator: {random_indicator}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using latest `run=2024-12-08T00.00.00Z`.\n", + "Using `forecast_horizons=[1]`\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
latitudelongituderunforecast_horizonunknown
051.08-5.142024-12-080 days 01:00:000.195312
151.08-5.132024-12-080 days 01:00:000.162109
251.08-5.122024-12-080 days 01:00:000.101562
351.08-5.112024-12-080 days 01:00:000.052734
451.08-5.102024-12-080 days 01:00:000.025391
..................
143422041.349.522024-12-080 days 01:00:000.000000
143422141.349.532024-12-080 days 01:00:000.000000
143422241.349.542024-12-080 days 01:00:000.000000
143422341.349.552024-12-080 days 01:00:000.000000
143422441.349.562024-12-080 days 01:00:000.000000
\n", + "

1434225 rows × 5 columns

\n", + "
" + ], + "text/plain": [ + " latitude longitude run forecast_horizon unknown\n", + "0 51.08 -5.14 2024-12-08 0 days 01:00:00 0.195312\n", + "1 51.08 -5.13 2024-12-08 0 days 01:00:00 0.162109\n", + "2 51.08 -5.12 2024-12-08 0 days 01:00:00 0.101562\n", + "3 51.08 -5.11 2024-12-08 0 days 01:00:00 0.052734\n", + "4 51.08 -5.10 2024-12-08 0 days 01:00:00 0.025391\n", + "... ... ... ... ... ...\n", + "1434220 41.34 9.52 2024-12-08 0 days 01:00:00 0.000000\n", + "1434221 41.34 9.53 2024-12-08 0 days 01:00:00 0.000000\n", + "1434222 41.34 9.54 2024-12-08 0 days 01:00:00 0.000000\n", + "1434223 41.34 9.55 2024-12-08 0 days 01:00:00 0.000000\n", + "1434224 41.34 9.56 2024-12-08 0 days 01:00:00 0.000000\n", + "\n", + "[1434225 rows x 5 columns]" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# fetch data using default computed params\n", + "arome.get_coverage(random_indicator)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "meteole_env", + "language": "python", + "name": "python3" + }, + "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.11.11" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/tutorial/arpege.ipynb b/tutorial/arpege.ipynb new file mode 100644 index 0000000..d56aeac --- /dev/null +++ b/tutorial/arpege.ipynb @@ -0,0 +1,297 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ARPEGE" + ] + }, + { + "cell_type": "code", + "execution_count": 42, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "import dotenv\n", + "import random\n", + "from meteole import ArpegeForecast\n", + "\n", + "dotenv.load_dotenv()\n", + "\n", + "application_id = os.getenv(\"APPLICATION_ID\")" + ] + }, + { + "cell_type": "code", + "execution_count": 44, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Fetching all available coverages...\n", + "Fetching all available coverages...\n", + "\n", + "\t Successfully fetched 1890 coverages,\n", + "\t representing 47 different indicators,\n", + "\t across the last 18 runs (from 2024-12-07T00.00.00Z to 2024-12-11T06.00.00Z).\n", + "\n", + "\t Default run for `get_coverage`: 2024-12-11T06.00.00Z)\n", + "\n", + "\t Successfully fetched 1890 coverages,\n", + "\t representing 47 different indicators,\n", + "\t across the last 18 runs (from 2024-12-07T00.00.00Z to 2024-12-11T06.00.00Z).\n", + "\n", + "\t Default run for `get_coverage`: 2024-12-11T06.00.00Z)\n" + ] + } + ], + "source": [ + "# init client\n", + "arpege = ArpegeForecast(application_id=application_id)" + ] + }, + { + "cell_type": "code", + "execution_count": 45, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Indicator: PRESSURE__SPECIFIC_HEIGHT_LEVEL_ABOVE_GROUND\n" + ] + } + ], + "source": [ + "# pick a random indicator\n", + "\n", + "random_indicator = random.choice(arpege.indicators)\n", + "print(f\"Indicator: {random_indicator}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Using latest `run=2024-12-07T00.00.00Z`.\n", + "Using latest `run=2024-12-07T00.00.00Z`.\n", + "Using `pressures=[100]`\n", + "Using `pressures=[100]`\n", + "Using `forecast_horizons=[0]`\n", + "Using `forecast_horizons=[0]`\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
latitudelongituderunforecast_horizonpressurew
051.0-5.12024-12-070 days100.00.143111
151.0-5.02024-12-070 days100.00.137038
251.0-4.92024-12-070 days100.00.124403
351.0-4.82024-12-070 days100.00.104933
451.0-4.72024-12-070 days100.00.079207
.....................
1425441.49.12024-12-070 days100.0-0.046007
1425541.49.22024-12-070 days100.0-0.031786
1425641.49.32024-12-070 days100.0-0.022081
1425741.49.42024-12-070 days100.0-0.016130
1425841.49.52024-12-070 days100.0-0.012743
\n", + "

14259 rows × 6 columns

\n", + "
" + ], + "text/plain": [ + " latitude longitude run forecast_horizon pressure w\n", + "0 51.0 -5.1 2024-12-07 0 days 100.0 0.143111\n", + "1 51.0 -5.0 2024-12-07 0 days 100.0 0.137038\n", + "2 51.0 -4.9 2024-12-07 0 days 100.0 0.124403\n", + "3 51.0 -4.8 2024-12-07 0 days 100.0 0.104933\n", + "4 51.0 -4.7 2024-12-07 0 days 100.0 0.079207\n", + "... ... ... ... ... ... ...\n", + "14254 41.4 9.1 2024-12-07 0 days 100.0 -0.046007\n", + "14255 41.4 9.2 2024-12-07 0 days 100.0 -0.031786\n", + "14256 41.4 9.3 2024-12-07 0 days 100.0 -0.022081\n", + "14257 41.4 9.4 2024-12-07 0 days 100.0 -0.016130\n", + "14258 41.4 9.5 2024-12-07 0 days 100.0 -0.012743\n", + "\n", + "[14259 rows x 6 columns]" + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# fetch data using default computed params\n", + "arpege.get_coverage(\"VERTICAL_VELOCITY_PRESSURE__ISOBARIC_SURFACE\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "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.11.9" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}