├── .github └── workflows │ ├── docs.yml │ └── main.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── MANIFEST.in ├── README.md ├── SECURITY.md ├── debian ├── changelog ├── compat ├── control ├── copyright ├── rules └── source │ └── format ├── docs ├── README.md ├── content │ ├── index.md │ ├── pygeoapi-plugin.md │ ├── reference │ │ ├── extensions │ │ │ ├── iso19139-2.md │ │ │ ├── iso19139-hnap.md │ │ │ ├── wmo-cmp.md │ │ │ ├── wmo-wcmp2.md │ │ │ └── wmo-wigos.md │ │ ├── index.md │ │ └── mcf.md │ └── tutorial.md └── mkdocs.yml ├── pygeometa ├── __init__.py ├── cli_options.py ├── core.py ├── helpers.py ├── pygeoapi_plugin.py └── schemas │ ├── __init__.py │ ├── base.py │ ├── common │ └── iso19139-charstring.j2 │ ├── dcat │ └── __init__.py │ ├── iso19139 │ ├── __init__.py │ ├── contact.j2 │ └── main.j2 │ ├── iso19139_2 │ ├── __init__.py │ ├── contact.j2 │ └── main.j2 │ ├── iso19139_hnap │ ├── __init__.py │ ├── charstring.j2 │ ├── contact.j2 │ └── main.j2 │ ├── mcf │ ├── core.yaml │ ├── iso19139_2.yaml │ ├── wmo-cmp.yaml │ ├── wmo-wcmp2.yaml │ └── wmo-wigos.yaml │ ├── ogcapi_records │ └── __init__.py │ ├── stac │ └── __init__.py │ ├── wmo_cmp │ ├── __init__.py │ ├── contact.j2 │ └── main.j2 │ ├── wmo_wcmp2 │ └── __init__.py │ └── wmo_wigos │ ├── __init__.py │ ├── contact.j2 │ └── main.j2 ├── requirements-dev.txt ├── requirements.txt ├── sample-wmo-wigos.yml ├── sample.yml ├── setup.py └── tests ├── bad-version.yml ├── base-distribution.yml ├── base-metadata.yml ├── broken-yaml.yml ├── child.yml ├── contact.yml ├── dates-pre-1900.yml ├── deep-nest-child.yml ├── deep-nest-parent.yml ├── md-SMJP01RJTD-gmd.xml ├── missing-version.yml ├── nil-identification-language.yml ├── run_tests.py ├── sample-child.yml ├── sample_schema └── __init__.py ├── sample_schema_j2 ├── contact.j2 └── main.j2 ├── unilingual.yml └── x-wmo-md-int.wmo.wis.ISMD01EDZW.xml /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: docs ⚙️ 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths: 8 | - 'docs/**' 9 | 10 | jobs: 11 | build: 12 | name: Build and Deploy Documentation 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@master 16 | - uses: actions/setup-python@v5 17 | with: 18 | python-version: '3.11' 19 | - name: Install requirements 📦 20 | run: | 21 | python -m pip install --upgrade pip 22 | pip install mkdocs 23 | - name: Deploy 📦 24 | run: cd docs && mkdocs gh-deploy --force -m 'update website' 25 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build ⚙️ 2 | 3 | on: [ push, pull_request ] 4 | 5 | jobs: 6 | main: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.10", "3.11"] 11 | steps: 12 | - uses: actions/checkout@master 13 | - uses: actions/setup-python@v5 14 | name: Setup Python ${{ matrix.python-version }} 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install system dependencies 📦 18 | run: sudo apt-get update && sudo apt-get install -y dh-python devscripts fakeroot debhelper python3-all python3-setuptools build-essential 19 | - name: Install requirements 📦 20 | run: | 21 | python3 -m pip install --upgrade pip 22 | pip3 install -r requirements-dev.txt 23 | - name: Install package 📦 24 | run: python3 setup.py install 25 | - name: run tests ⚙️ 26 | run: python3 setup.py test 27 | - name: run test coverage ⚙️ 28 | run: | 29 | coverage run --source pygeometa setup.py test 30 | coverage report -m 31 | - name: build docs 🏗️ 32 | run: mkdocs build -f docs/mkdocs.yml 33 | - name: run flake8 ⚙️ 34 | run: flake8 35 | - name: build Python package 🏗️ 36 | run: python3 setup.py sdist bdist_wheel --universal 37 | - name: build Debian package 🏗️ 38 | run: sudo debuild -b -uc -us 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | .venv 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # Installer logs 27 | pip-log.txt 28 | pip-delete-this-directory.txt 29 | 30 | # Unit test / coverage reports 31 | htmlcov/ 32 | .tox/ 33 | .coverage 34 | .cache 35 | nosetests.xml 36 | coverage.xml 37 | 38 | # Translations 39 | *.mo 40 | 41 | # Sphinx documentation 42 | docs/_build/ 43 | docs/site 44 | 45 | # pygeometa 46 | *.mcf 47 | *.xml 48 | MANIFEST 49 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at tomkralidis@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to pygeometa 2 | 3 | We welcome contributions to pygeometa, in the form of issues, bug fixes, or 4 | suggestions for enhancements. This document sets out our guidelines and best 5 | practices for such contributions. 6 | 7 | It's based on the [Contributing to Open Source Projects 8 | Guide](https://contribution-guide-org.readthedocs.io/). 9 | 10 | pygeometa has the following modes of contribution: 11 | 12 | - GitHub Commit Access 13 | - GitHub Pull Requests 14 | 15 | ## Code of Conduct 16 | 17 | Contributors to this project are expected to act respectfully toward others in accordance with the [OSGeo Code of Conduct](https://www.osgeo.org/code_of_conduct). 18 | 19 | ## Submitting Bugs 20 | 21 | ### Due Diligence 22 | 23 | Before submitting a bug, please do the following: 24 | 25 | * Perform __basic troubleshooting__ steps: 26 | 27 | * __Make sure you're on the latest version.__ If you're not on the most 28 | recent version, your problem may have been solved already! Upgrading is 29 | always the best first step. 30 | * [__Search the issue 31 | tracker__](https://github.com/geopython/pygeometa/issues) 32 | to make sure it's not a known issue. 33 | 34 | ### What to put in your bug report 35 | 36 | Make sure your report gets the attention it deserves: bug reports with missing 37 | information may be ignored or punted back to you, delaying a fix. The below 38 | constitutes a bare minimum; more info is almost always better: 39 | 40 | * __What version of Python are you using?__ For example, are you using Python 41 | 2.7, Python 3.7, PyPy 2.0? 42 | * __What operating system are you using?__ Windows (7, 8, 10, 32-bit, 64-bit), 43 | Mac OS X, (10.7.4, 10.9.0), GNU/Linux (which distribution, which version?) 44 | Again, more detail is better. 45 | * __Which version or versions of the software are you using?__ Ideally, you've 46 | followed the advice above and are on the latest version, but please confirm 47 | this. 48 | * __How can the we recreate your problem?__ Imagine that we have never used 49 | pygeometa before and have downloaded it for the first time. Exactly what steps 50 | do we need to take to reproduce your problem? 51 | 52 | ## Contributions and Licensing 53 | 54 | ### Contributor License Agreement 55 | 56 | Your contribution will be under our [license](https://github.com/geopython/pygeometa/blob/master/LICENSE.md) as per [GitHub's terms of service](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license). 57 | 58 | ### GitHub Commit Access 59 | 60 | * Proposals to provide developers with GitHub commit access shall be emailed to the pygeometa [mailing list](https://lists.osgeo.org/mailman/listinfo/pygeometa). Proposals shall be approved by the pygeometa development team. Committers shall be added by the project admin. 61 | * Removal of commit access shall be handled in the same manner. 62 | 63 | ### GitHub Pull Requests 64 | 65 | * Pull requests may include copyright in the source code header by the contributor if the contribution is significant or the contributor wants to claim copyright on their contribution. 66 | * All contributors shall be listed at https://github.com/geopython/pygeometa/graphs/contributors 67 | * Unclaimed copyright, by default, is assigned to the main copyright holders as specified in https://github.com/geopython/pygeometa/blob/master/LICENSE.md 68 | 69 | ### Version Control Branching 70 | 71 | * Always __make a new branch__ for your work, no matter how small. This makes 72 | it easy for others to take just that one set of changes from your repository, 73 | in case you have multiple unrelated changes floating around. 74 | 75 | * __Don't submit unrelated changes in the same branch/pull request!__ If it 76 | is not possible to review your changes quickly and easily, we may reject 77 | your request. 78 | 79 | * __Base your new branch off of the appropriate branch__ on the main repository: 80 | 81 | * In general the released version of pygeometa is based on the ``master`` 82 | (default) branch whereas development work is done under other non-default 83 | branches. Unless you are sure that your issue affects a non-default 84 | branch, __base your branch off the ``master`` one__. 85 | 86 | * Note that depending on how long it takes for the dev team to merge your 87 | patch, the copy of ``master`` you worked off of may get out of date! 88 | * If you find yourself 'bumping' a pull request that's been sidelined for a 89 | while, __make sure you rebase or merge to latest ``master``__ to ensure a 90 | speedier resolution. 91 | 92 | ### Code Formatting 93 | 94 | * __Please follow the coding conventions and style used in the pygeometa repository.__ 95 | * pygeometa endeavours to follow the 96 | [PEP-8](http://www.python.org/dev/peps/pep-0008/) guidelines. 97 | 98 | ## Suggesting Enhancements 99 | 100 | We welcome suggestions for enhancements, but reserve the right to reject them 101 | if they do not follow future plans for pygeometa. 102 | 103 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Terms and Conditions of Use 2 | =========================== 3 | 4 | Unless otherwise noted, computer program source code of this distribution 5 | is covered under Crown Copyright, Government of Canada, and is distributed 6 | under the MIT License. 7 | 8 | The Canada wordmark and related graphics associated with this distribution 9 | are protected under trademark law and copyright law. No permission is granted 10 | to use them outside the parameters of the Government of Canada's corporate 11 | identity program. For more information, see 12 | http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 13 | 14 | Copyright title to all 3rd party software distributed with this software 15 | is held by the respective copyright holders as noted in those files. Users 16 | are asked to read the 3rd Party Licenses referenced with those assets. 17 | 18 | 19 | The MIT License (MIT) 20 | ===================== 21 | 22 | Copyright (c) 2025 Government of Canada 23 | 24 | Permission is hereby granted, free of charge, to any person 25 | obtaining a copy of this software and associated documentation 26 | files (the "Software"), to deal in the Software without 27 | restriction, including without limitation the rights to use, 28 | copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | copies of the Software, and to permit persons to whom the 30 | Software is furnished to do so, subject to the following 31 | conditions: 32 | 33 | The above copyright notice and this permission notice shall be 34 | included in all copies or substantial portions of the Software. 35 | 36 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 38 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 41 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 42 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 43 | OTHER DEALINGS IN THE SOFTWARE. 44 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include pygeometa *.j2 *.yaml 2 | include LICENSE.md 3 | include README.md 4 | include requirements.txt 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/geopython/pygeometa/workflows/build%20%E2%9A%99%EF%B8%8F/badge.svg)](https://github.com/geopython/pygeometa/actions) 2 | [![Join the chat at https://matrix.to/#/#geopython_pygeometa:gitter.im](https://badges.gitter.im/Join%20Chat.svg)](https://matrix.to/#/#geopython_pygeometa:gitter.im) 3 | 4 | # pygeometa 5 | 6 | [pygeometa](https://geopython.github.io/pygeometa) is a Python package to 7 | generate metadata for geospatial datasets. 8 | 9 | ## Installation 10 | 11 | pygeometa is best installed and used within a Python virtualenv. 12 | 13 | ### Requirements 14 | 15 | * Python 3 and above 16 | * Python [virtualenv](https://virtualenv.pypa.io/) package 17 | 18 | ### Dependencies 19 | 20 | Dependencies are listed in [requirements.txt](requirements.txt). Dependencies 21 | are automatically installed during pygeometa's installation. 22 | 23 | ### Installing the Package 24 | 25 | ```bash 26 | python3 -m venv my-env 27 | cd my-env 28 | . bin/activate 29 | git clone https://github.com/geopython/pygeometa.git 30 | cd pygeometa 31 | python3 setup.py build 32 | python3 setup.py install 33 | ``` 34 | 35 | ## Running 36 | 37 | ### From the command line 38 | 39 | ```bash 40 | # show all subcommands 41 | pygeometa 42 | 43 | # show all supported schemas 44 | pygeometa metadata schemas 45 | 46 | # provide a basic sanity check/report on an MCF 47 | pygeometa metadata info path/to/file.yml 48 | 49 | # generate an ISO 19139 document to stdout 50 | pygeometa metadata generate path/to/file.yml --schema=iso19139 51 | 52 | # generate an ISO 19139 document to disk 53 | pygeometa metadata generate path/to/file.yml --schema=iso19139 --output=some_file.xml 54 | 55 | # generate an ISO 19139 document to disk with debugging (ERROR, WARNING, INFO, DEBUG) 56 | pygeometa metadata generate path/to/file.yml --schema=iso19139 --output=some_file.xml --verbosity=DEBUG # add verbose (ERROR, WARNING, INFO, DEBUG) 57 | 58 | # use your own defined schema 59 | pygeometa metadata generate path/to/file.yml --schema_local=/path/to/my-schema --output=some_file.xml # to file 60 | 61 | # validate your MCF 62 | pygeometa metadata validate path/to/file.yml 63 | 64 | # import a metadata document to MCF 65 | pygeometa metadata import path/to/file.xml --schema=iso19139 66 | 67 | # import a metadata document to MCF, autodetecting the metadata file format 68 | pygeometa metadata import path/to/file.xml --schema=autodetect # --schema=autodetect is default 69 | 70 | # transform from one metadata representation to another 71 | pygeometa metadata transform path/to/file.xml --input-schema=iso19139 --output-schema=oarec-record 72 | 73 | # transform from one metadata representation to another, autodetecting the metadata file format 74 | pygeometa metadata transform path/to/file.xml --input-schema=autodetect --output-schema=oarec-record # --input-schema=autodetect is default 75 | ``` 76 | 77 | ### Supported schemas 78 | Schemas supported by pygeometa: 79 | * dcat, [reference](https://www.w3.org/TR/vocab-dcat-2/) 80 | * iso19139, [reference](http://www.iso.org/iso/catalogue_detail.htm?csnumber=32557) 81 | * iso19139-hnap, [reference](http://www.gcpedia.gc.ca/wiki/Federal_Geospatial_Platform/Policies_and_Standards/Catalogue/Release/Appendix_B_Guidelines_and_Best_Practices/Guide_to_Harmonized_ISO_19115:2003_NAP) 82 | * OGC API - Records - Part 1: Core, record model, [reference](https://github.com/opengeospatial/ogcapi-records/blob/master/core/openapi/schemas/record.yaml) 83 | * SpatioTemporal Asset Catalog [(STAC)](https://stacspec.org) 84 | * iso19139-2, [reference](https://www.iso.org/standard/67039.html) 85 | * [wmo-cmp](doc/content/reference/formats/wmo-cmp.md), [reference](http://wis.wmo.int/2013/metadata/version_1-3-0/WMO_Core_Metadata_Profile_v1.3_Part_1.pdf) 86 | * [wmo-wcmp2](doc/content/reference/formats/wmo-wcmp2.md), [reference](https://wmo-im.github.io/wcmp2) 87 | * [wmo-wigos](doc/content/reference/formats/wmo-wigos.md), [reference](https://library.wmo.int/opac/doc_num.php?explnum_id=3653) 88 | * Local schema, specified with ```--schema_local=/path/to/my-schema``` 89 | 90 | ### Using the API from Python 91 | 92 | ```python 93 | from pygeometa.core import read_mcf, render_j2_template 94 | 95 | # read from disk 96 | mcf_dict = read_mcf('/path/to/file.yml') 97 | # read from string 98 | mcf_dict = read_mcf(mcf_string) 99 | 100 | # choose ISO 19139 output schema 101 | from pygeometa.schemas.iso19139 import ISO19139OutputSchema 102 | iso_os = ISO19139OutputSchema() 103 | 104 | # default schema 105 | xml_string = iso_os.write(mcf_dict) 106 | 107 | # user-defined schema 108 | xml_string = render_j2_template(mcf_dict, template_dir='/path/to/new-schema') 109 | 110 | # write to disk 111 | with open('output.xml', 'wb') as ff: 112 | ff.write(xml_string) 113 | ``` 114 | 115 | ## Development 116 | 117 | ### Setting up a Development Environment 118 | 119 | Same as installing a package. Use a virtualenv. Also install developer 120 | requirements: 121 | 122 | ```bash 123 | pip3 install -r requirements-dev.txt 124 | ``` 125 | 126 | ### Adding a Metadata Schema to the Core 127 | 128 | Adding an output metadata schemas to pygeometa involves extending 129 | `pygeometa.schemas.base.BaseOutputSchema` and supporting the `write` 130 | function to return a string of exported metadata content. If you are using 131 | Jinja2 templates, see the next section. If you are using another means of 132 | generating metadata (lxml, xml.etree, json, etc.), override the ABC `write` 133 | class to emit a string using your tooling/workflow accordingly. See the 134 | below sections for examples. 135 | 136 | Once you have added your metadata schema, you need to register it with 137 | pygeometa's schema registry: 138 | 139 | ```bash 140 | vi pygeometa/schemas/__init__.py 141 | # edit the SCHEMAS dict with the metadata schema name and dotted path of class 142 | ``` 143 | 144 | #### Jinja2 templates 145 | 146 | To add support for a new metadata schema using Jinja2 templates: 147 | ```bash 148 | cp -r pygeometa/schemas/iso19139 pygeometa/schemas/new-schema 149 | ``` 150 | Then modify `*.j2` files in the new `pygeometa/schemas/new-schema` directory 151 | to comply to new metadata schema. 152 | 153 | #### Custom tooling 154 | 155 | To add support for a new metadata schemas using other tooling/workflow: 156 | ```bash 157 | mkdir pygeometa/schemas/foo 158 | cp pygeometa/schemas/iso19139/__init__.py pygeometa/schemas/foo 159 | vi pygeometa/schemas/foo/__init__.py 160 | # update class name and super().__init__() function accordingly 161 | ``` 162 | 163 | ### Running Tests 164 | 165 | ```bash 166 | # via setuptools 167 | python3 setup.py test 168 | # manually 169 | cd tests 170 | python3 run_tests.py 171 | ``` 172 | 173 | ## Releasing 174 | 175 | ```bash 176 | # update version 177 | vi pygeometa/__init__.py 178 | vi debian/changelog # add changelog entry and summary of updates 179 | git commit -m 'update release version' pygeometa/__init__.py debian/changelog 180 | # push changes 181 | git push origin master 182 | git tag -a x.y.z -m 'tagging release x.y.z' 183 | # push tag 184 | git push --tags 185 | rm -fr build dist *.egg-info 186 | python3 setup.py sdist bdist_wheel --universal 187 | twine upload dist/* 188 | ``` 189 | 190 | ### Code Conventions 191 | 192 | * [PEP8](https://www.python.org/dev/peps/pep-0008) 193 | 194 | ### Bugs and Issues 195 | 196 | All bugs, enhancements and issues are managed on [GitHub](https://github.com/geopython/pygeometa/issues). 197 | 198 | ## Contact 199 | 200 | * [Tom Kralidis](https://github.com/tomkralidis) 201 | * [Alexandre Leroux](https://github.com/alexandreleroux) 202 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # pygeometa Security Policy 2 | 3 | ## Reporting 4 | 5 | Security/vulnerability reports **should not** be submitted through GitHub issues or public discussions, but instead please send your report 6 | to **geopython-security nospam @ lists.osgeo.org** - (remove the blanks and 'nospam'). 7 | 8 | ## Supported Versions 9 | 10 | pygeometa developers will release patches for security vulnerabilities for the following versions: 11 | 12 | | Version | Supported | 13 | | ------- | ------------------ | 14 | | latest stable version | :white_check_mark: | 15 | | previous versions | :x: | 16 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | pygeometa (0.16.1) jammy; urgency=medium 2 | 3 | * OARec: time and created/updated date-time fixes 4 | 5 | -- Tom Kralidis Tue, 27 Aug 2024 01:56:22 +0000 6 | 7 | pygeometa (0.16.0) focal; urgency=medium 8 | 9 | * MCF core: make identification topiccategory, fees, accessconstraints, maintenancefrequency optional (#242) 10 | * OARec: write keywords as theme/concepts if vocabulary exists (#233) 11 | * OARec: safeguard properties.rights 12 | * WMO WCMP2: various fixes based on specification updates 13 | * CLI: add -o as short option for --output 14 | * ISO: distribution: ensure format_version exists or emit gco:nilReason=missing (#240) 15 | 16 | -- Tom Kralidis Sun, 10 Mar 2024 23:07:13 +0000 17 | 18 | pygeometa (0.15.3) focal; urgency=medium 19 | 20 | * OARec bugfixes 21 | 22 | -- Tom Kralidis Fri, 15 Sep 2023 20:15:36 +0000 23 | 24 | pygeometa (0.15.2) focal; urgency=medium 25 | 26 | * OARec bugfix 27 | 28 | -- Tom Kralidis Fri, 15 Sep 2023 12:17:32 +0000 29 | 30 | pygeometa (0.15.1) focal; urgency=medium 31 | 32 | * fixes to OARec and WCMP import/export 33 | * documentation fixes 34 | 35 | -- Tom Kralidis Fri, 15 Sep 2023 11:17:42 +0000 36 | 37 | pygeometa (0.15.0) focal; urgency=medium 38 | 39 | * add support for detailed schema information 40 | * add thesaurus handling to ISO import 41 | 42 | -- Tom Kralidis Sat, 20 May 2023 11:27:33 +0000 43 | 44 | pygeometa (0.14.0) focal; urgency=medium 45 | 46 | * add metadata transformation workflow (#186) 47 | * MCF schema updates (#187) 48 | * fix ISO based distribution model (#175) 49 | * add WMO WCMP2 updates 50 | * improve ISO contact parsing (#207) 51 | * add identification.license to MCF (#205) 52 | * update OARec themes/concepts model (#213) 53 | * add support for spatial keywords to MCF (#202) 54 | * streamline OARec contacts (@215) 55 | * update ISO parsing based on OWSLib 0.29 56 | 57 | -- Tom Kralidis Mon, 10 Apr 2023 01:05:25 +0000 58 | 59 | pygeometa (0.13.1) bionic; urgency=medium 60 | 61 | * fix pygeoapi media type and payload for raw responses 62 | * fix STAC and DCAT write() function signatures 63 | 64 | -- Tom Kralidis Fri, 30 Sep 2022 14:25:58 +0000 65 | 66 | pygeometa (0.13.0) bionic; urgency=medium 67 | 68 | * add environment variable support for MCFs 69 | * add pygeoapi plugin 70 | 71 | -- Tom Kralidis Thu, 29 Sep 2022 14:38:35 +0000 72 | 73 | pygeometa (0.12.0) bionic; urgency=medium 74 | 75 | * fix OARec temporal extent date casting 76 | * update OARec output to latest version of draft specification 77 | * add support for WMO Core Metadata Profile 2 (WCMP2) 78 | * fix ISO contact handling 79 | * add temporal resolution support 80 | * fix DCAT reference error 81 | 82 | -- Tom Kralidis Sun, 28 Aug 2022 11:56:28 +0000 83 | 84 | pygeometa (0.11.1) bionic; urgency=medium 85 | 86 | * fix ISO gmd:function handling 87 | * fix OARec generation based on latest specification updates 88 | 89 | -- Tom Kralidis Thu, 16 Jun 2022 13:56:28 +0000 90 | 91 | pygeometa (0.11.0) bionic; urgency=medium 92 | 93 | * add link relation support to MCF model 94 | 95 | -- Tom Kralidis Tue, 7 Jun 2022 22:44:10 +0000 96 | 97 | pygeometa (0.10.0) bionic; urgency=medium 98 | 99 | * add ISO importer 100 | * add support for MCF attributes 101 | * dump import as YAML to CLI 102 | * add identification.edition to MCF model 103 | 104 | -- Tom Kralidis Sat, 28 May 2022 10:15:25 +0000 105 | 106 | pygeometa (0.9.1) bionic; urgency=medium 107 | 108 | * fix OARec associations 109 | 110 | -- Tom Kralidis Tue, 1 Mar 2022 1:14:35 +0000 111 | 112 | pygeometa (0.9.0) bionic; urgency=medium 113 | 114 | * OARec model updates 115 | * fix schema write stringify support 116 | * fix keyword vocabulary ref 117 | * fix validation of nested MCFs 118 | * add WMO thesaurus to ISO-based keywords 119 | * align contact keys to ISO/STAC roles 120 | 121 | -- Tom Kralidis Sun, 6 Feb 2022 21:18:53 +0000 122 | 123 | pygeometa (0.8.0) bionic; urgency=medium 124 | 125 | * update multilingual support 126 | * add MCF schema support 127 | 128 | -- Tom Kralidis Sat, 11 Sep 2021 11:52:18 +0000 129 | 130 | pygeometa (0.7.0) bionic; urgency=medium 131 | 132 | * support keyword vocabulary 133 | * fix ISO unique IDs 134 | * add support for OGC API - Records metadata model 135 | * add support for ISO gmd:dataQualityInfo 136 | * WCMP: update templates to align with WMO KPIs 137 | * fall gracefully on null dates 138 | 139 | -- Tom Kralidis Fri, 3 Sep 2021 13:01:35 +0000 140 | 141 | pygeometa (0.6.0) bionic; urgency=medium 142 | 143 | * numerous new WIGOS elements and fixes 144 | * add info subcommand 145 | * refactor common CLI options 146 | * refactor output schema for additional non-XML formats 147 | * remove migration tool for old mcf format 148 | * documentation updates 149 | * move from Travis CI to GitHub Actions 150 | * add support for 19115-2 151 | * add support for dcat as json-ld 152 | 153 | -- Tom Kralidis Sat, 16 Jan 2021 14:37:13 +0000 154 | 155 | pygeometa (0.5.0) bionic; urgency=medium 156 | 157 | * initial Debian packaging 158 | 159 | -- Tom Kralidis Thu, 12 Nov 2020 11:21:07 +0000 160 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: pygeometa 2 | Section: python 3 | Priority: optional 4 | Maintainer: Tom Kralidis 5 | Uploaders: Angelos Tzotsos 6 | Build-Depends: debhelper (>= 9), 7 | dh-python, 8 | python3-all, 9 | python3-setuptools 10 | Standards-Version: 4.3.0 11 | Vcs-Git: https://github.com/geopython/pygeometa.git 12 | Homepage: https://geopython.github.io/pygeometa 13 | 14 | Package: python3-pygeometa 15 | Architecture: all 16 | Depends: ${python3:Depends}, 17 | python3-click, 18 | python3-jinja2, 19 | python3-jsonschema, 20 | python3-lxml, 21 | python3-owslib, 22 | python3-yaml, 23 | ${misc:Depends} 24 | Description: Python package to generate metadata for geospatial datasets. 25 | pygeometa is a Python package to generate metadata for geospatial datasets. 26 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://github.com/geopython/pygeometa 3 | 4 | Files: * 5 | Copyright: Copyright 2015 Government of Canada 6 | Copyright: Copyright 2020 Tom Kralidis 7 | License: MIT 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the "Software"), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or 13 | sell copies of the Software, and to permit persons to whom 14 | the Software is furnished to do so, subject to the following 15 | conditions: 16 | . 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | . 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | #export DH_VERBOSE=1 6 | 7 | export PYBUILD_NAME=pygeometa 8 | 9 | %: 10 | dh $@ --with python3 --buildsystem pybuild 11 | 12 | override_dh_auto_test: 13 | @echo "nocheck set, not running tests" 14 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # pygeometa Website 2 | 3 | The pygeometa [website](https://geopython.github.io/pygeometa/) is powered 4 | by [MkDocs](https://www.mkdocs.org) which facilitates easy management 5 | of website content and publishing. 6 | 7 | ## Setting up the website environment locally 8 | 9 | ```bash 10 | # build a virtual Python environment in isolation 11 | python3 -m venv pygeometa 12 | cd pygeometa 13 | # download pygeometa from GitHub 14 | git clone https://github.com/geopython/pygeometa.git 15 | # install required dependencies 16 | pip3 install -r requirements-dev.txt 17 | cd pygeometa/doc 18 | # build the website 19 | mkdocs build 20 | # serve locally 21 | mkdocs serve # website is made available on http://localhost:8000/ 22 | ``` 23 | 24 | ## Content management workflow 25 | 26 | ### Overview 27 | 28 | To manage content you require an account on GitHub. From here you can either 29 | 1. fork the repository, make your own changes and issue a pull request, or 2. 30 | edit the content directly. For option 2 the necessary permissions are required. 31 | 32 | The basic workflow is as follows: 33 | 34 | - manage content 35 | - commit updates 36 | - publish to the live site 37 | 38 | ### Adding a page 39 | 40 | ```bash 41 | vi content/new-page.md # add content 42 | vi mkdocs.yml # add to navigation section 43 | # edit any other files necessary which may want to link to the new page 44 | git add content/new-page.md 45 | git commit -m 'add new page on topic x' content/new-page.md mkdocs.yml 46 | git push origin master 47 | ``` 48 | 49 | ### Updating a page 50 | 51 | ```bash 52 | vi content/page.md # update content 53 | git commit -m 'update page' content/page.md 54 | git push origin master 55 | ``` 56 | 57 | ## Publishing updates to the live site 58 | 59 | ```bash 60 | # NOTE: you require access privileges to the GitHub repository 61 | # to publish live updates 62 | mkdocs gh-deploy -m 'add new page on topic x' 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/content/index.md: -------------------------------------------------------------------------------- 1 | ## pygeometa 2 | 3 | [![Join the chat at https://matrix.to/#/#geopython_pygeometa:gitter.im](https://badges.gitter.im/Join%20Chat.svg)](https://matrix.to/#/#geopython_pygeometa:gitter.im) 4 | 5 |

Metadata Creation for the Rest of Us

6 | 7 | pygeometa provides a lightweight and Pythonic approach for users to easily 8 | create geospatial metadata in standards-based formats using simple 9 | configuration files (affectionately called Metadata Control Files [MCF]). 10 | Leveraging the simple but powerful YAML format, pygeometa can generate metadata 11 | in numerous standards. Users can also create their own custom metadata formats 12 | which can be plugged into pygeometa for custom metadata format output. 13 | 14 | For developers, pygeometa provides a Pythonic API that allows developers to 15 | tightly couple metadata generation within their systems and integrate nicely 16 | into metadata production pipelines. 17 | 18 | The project supports various metadata formats out of the box including ISO 19 | 19115, the WMO Core Metadata Profile, and the WIGOS Metadata Standard. Can't 20 | find the format you're looking for? Element(s) missing from a given format? 21 | Feel free to open an [issue](https://github.com/geopython/pygeometa/issues)! 22 | 23 | pygeometa has minimal dependencies (wheel install is less than 100 kB), and provides 24 | a flexible extension mechanism leveraging the Jinja2 templating system. 25 | 26 | pygeometa is [open source](https://opensource.org) and released under an 27 | [MIT license](https://github.com/geopython/pygeometa/blob/master/LICENSE.md). 28 | 29 | ## Features 30 | * simple YAML-based configuration 31 | * real-time MCF validation 32 | * extensible: plugin architecture allows for easy addition of new metadata 33 | formats using Jinja2 templates or custom workflow (JSON, YAML, CSV, etc.) 34 | * flexible: use as a command-line tool or integrate as a library 35 | * import from external metadata sources 36 | * multilingual support 37 | 38 | ## History 39 | 40 | Started in 2009, pygeometa originated within an internal project called pygdm, 41 | which provided generic geospatial data management functions. pygdm (now end 42 | of life) was used for generating MSC/CMC geospatial metadata. pygeometa was 43 | pulled out of pygdm to focus on the core requirement of generating geospatial 44 | metadata within a real-time environment and automated workflows. 45 | 46 | In 2015 pygeometa was made publically available in support of the Treasury 47 | Board [Policy on Acceptable Network and Device Use](https://www.tbs-sct.gc.ca/pol/doc-eng.aspx?id=27122). 48 | -------------------------------------------------------------------------------- /docs/content/pygeoapi-plugin.md: -------------------------------------------------------------------------------- 1 | # pygeoapi plugin 2 | 3 | ## Overview 4 | 5 | pygeometa also provides a custom [pygeoapi](https://pygeoapi.io) processing plugin, providing 6 | pygeometa functionality via OGC API - Processes. 7 | 8 | ## Installation 9 | 10 | To integrate this plugin in pygeoapi: 11 | 12 | - ensure pygeometa is installed into the pygeoapi deployment environment 13 | 14 | - add the processes to the pygeoapi configuration as follows: 15 | 16 | ```yaml 17 | 18 | pygeometa-metadata-schemas: 19 | type: process 20 | processor: 21 | name: pygeometa.pygeoapi_plugin.PygeometaMetadataSchemasProcessor 22 | 23 | pygeometa-metadata-import: 24 | type: process 25 | processor: 26 | name: pygeometa.pygeoapi_plugin.PygeometaMetadataImportProcessor 27 | 28 | pygeometa-metadata-validate: 29 | type: process 30 | processor: 31 | name: pygeometa.pygeoapi_plugin.PygeometaMetadataValidateProcessor 32 | 33 | pygeometa-metadata-generate: 34 | type: process 35 | processor: 36 | name: pygeometa.pygeoapi_plugin.PygeometaMetadataGenerateProcessor 37 | 38 | pygeometa-metadata-transform: 39 | type: process 40 | processor: 41 | name: pygeometa.pygeoapi_plugin.PygeometaMetadataTransformProcessor 42 | ``` 43 | - regenerate the pygeoapi OpenAPI configuration 44 | 45 | ```bash 46 | pygeoapi openapi generate $PYGEOAPI_CONFIG --output-file $PYGEOAPI_OPENAPI 47 | ``` 48 | 49 | - restart pygeoapi 50 | 51 | ## Usage 52 | 53 | The resulting processes will be available at the following endpoints: 54 | 55 | * `/processes/pygeometa-metadata-import` 56 | * `/processes/pygeometa-metadata-validate` 57 | * `/processes/pygeometa-metadata-generate` 58 | * `/processes/pygeometa-metadata-transform` 59 | 60 | Note that pygeoapi's OpenAPI/Swagger interface (at `/openapi`) also 61 | provides a developer-friendly interface to test and run requests 62 | -------------------------------------------------------------------------------- /docs/content/reference/extensions/iso19139-2.md: -------------------------------------------------------------------------------- 1 | # ISO 19139-2 Schema Reference 2 | 3 | This document describes [ISO 19115-2/19139-2 Extensions for acquisition and processing](https://www.iso.org/standard/67039.html) schema extensions in pygeometa. 4 | 5 | ## Sections 6 | 7 | ### `acquisition` 8 | 9 | The `acquisition` object supports 1..n platform defintions. 10 | 11 | ### `acquisition.platforms` 12 | 13 | Property Name|Mandatory/Optional|Description|Example|Reference 14 | -------------|------------------|-----------|-------|---------: 15 | identifier|Mandatory|unique identification of the platform|LANDSAT_8|ISO 19115 Part 2 Section B.2.5.8 16 | description|Mandatory|platform description|My platform|ISO 19115 Part 2 Section B.2.5.8 17 | 18 | `acquisition.platforms` objects support 1..n instrument definitions. 19 | 20 | #### `acquisition.platforms[].instruments` 21 | 22 | Property Name|Mandatory/Optional|Description|Example|Reference 23 | -------------|------------------|-----------|-------|---------: 24 | identifier|Mandatory|instrument identifier|OLI_TIRS|ISO 19115 Part 2 B.2.5.4 25 | type|Mandatory|instrument type|INS-NOBS|ISO 19115 Part 2 B.2.5.4 26 | -------------------------------------------------------------------------------- /docs/content/reference/extensions/iso19139-hnap.md: -------------------------------------------------------------------------------- 1 | # HNAP Schema Reference 2 | 3 | This document describes HNAP schema extensions in pygeometa. 4 | 5 | ### `identification.keywords` 6 | 7 | HNAP support includes the following `keywords` sections. 8 | 9 | * `gc_cst`: Government of Canada Core Subject Thesaurus 10 | * `hnap_category_information`: HNAP 11 | * `hnap_category_geography`: HNAP 12 | * `hnap_category_content`: HNAP 13 | 14 | Keyword requirements are the same as pygeometa's default keyword rules. 15 | 16 | ## `distribution` 17 | 18 | Distribution identifier: 19 | 20 | * To comply with HNAP, distribution methods require its sections to be duplicated and appended with `_eng-CAN` and `_fra-CAN` to distribution names 21 | 22 | Distribution parameters: 23 | 24 | * Name of the distribution method needs to be specified with name: en and name: fr 25 | * Do not provide values for the 'description' parameter in the MCF file since HNAP requires a special description that is built by pygeometa 26 | * Content type needs to be bilingual and be a valid HNAP value 27 | * Valid values are: Web Service,Service Web,Dataset,Données,API,Application,Supporting Document,Document de soutien 28 | * Format needs to be bilingual and based on the valid HNAP values 29 | * Valid values are: AI. AMF,Application,ASCII Grid,BMP,CDED ASCII,CDR,CSV,DOC,dxf,E00,ECW,EDI,EMF,EPS,ESRI REST,EXE,FGDB / GDB,Flat raster binary,GeoPDF,GeoRSS,GeoTIF,GIF,GML,HDF,HTML,IATI,JPEG 2000,JPG,JSON,JSON Lines,KML / KMZ,NetCDF,ODP,ODS,ODT,PDF,PNG,PPT,RDF,RDFa,RSS,SAR / CCT,SAV,SEGY,SHP,SQL,SVG,TIFF,TXT,XLS,XLSM,XML,WFS,WMS,WMTS,Zip,Other 30 | * Format version needs to be specified with `format_version:` 31 | 32 | ## Example of distribution section 33 | 34 | Example of valid HNAP distribution sections: 35 | 36 | ```yaml 37 | distribution: 38 | waf_fra-CAN: 39 | url: https://dd.weather.gc.ca/model_gem_global/25km/grib2/lat_lon/ 40 | type: WWW:LINK 41 | name: 42 | en: MSC Datamart 43 | fr: Datamart du SMC 44 | hnap_contenttype: 45 | en: Dataset 46 | fr: Données 47 | format: 48 | en: Other 49 | fr: Autre 50 | format_version: '0' 51 | function: download 52 | 53 | waf_eng-CAN: 54 | url: https://dd.weather.gc.ca/model_gem_global/25km/grib2/lat_lon/ 55 | type: WWW:LINK 56 | name: 57 | en: MSC Datamart 58 | fr: Datamart du SMC 59 | hnap_contenttype: 60 | en: Dataset 61 | fr: Données 62 | format: 63 | en: Other 64 | fr: Autre 65 | format_version: '0' 66 | function: download 67 | 68 | wms_eng-CAN: 69 | url: https://geo.weather.gc.ca/geomet/?lang=E&service=WMS&request=GetCapabilities&layers=GDPS.ETA_TT 70 | hnap_contenttype: 71 | en: Web Service 72 | fr: Service Web 73 | type: OGC:WMS 74 | format: WMS 75 | format_version: '1.1.1' 76 | name: 77 | en: GDPS.ETA_TT 78 | fr: GDPS.ETA_TT 79 | function: download 80 | 81 | wms_fra-CAN: 82 | url: https://geo.weather.gc.ca/geomet/?lang=E&service=WMS&request=GetCapabilities&layers=GDPS.ETA_TT 83 | hnap_contenttype: 84 | en: Web Service 85 | fr: Service Web 86 | type: OGC:WMS 87 | format: WMS 88 | format_version: '1.1.1' 89 | name: 90 | en: GDPS.ETA_TT 91 | fr: GDPS.ETA_TT 92 | function: download 93 | ``` 94 | -------------------------------------------------------------------------------- /docs/content/reference/extensions/wmo-cmp.md: -------------------------------------------------------------------------------- 1 | # WMO Core Metadata Profile Schema Reference 2 | 3 | This document describes [WMO Core Metadata Profile](https://wis.wmo.int/2013/metadata/version_1-3-0/WMO_Core_Metadata_Profile_v1.3_Part_1.pdf) schema extensions in pygeometa. 4 | 5 | ## Sections 6 | 7 | ### `identification` 8 | 9 | Property Name|Mandatory/Optional|Description|Example|Reference 10 | -------------|------------------|-----------|-------|---------: 11 | otherconstraints_wmo_data_policy|Mandatory|WMO data policy statment from WMO_DataLicenseCode (must be one of 'WMOEssential', 'WMOAdditional' 'WMOOther')|WMOEssential|WMO Core Metadata Profile 1.3, Part 1, Section 9.3.1 12 | otherconstraints_wmo_gts_priority|Mandatory|WMO GTS priority (must be one of 'GTSPriority1', 'GTSPriority2', 'GTSPriority3', 'GTSPriority4')|GTSPriority2|WMO Core Metadata Profile 1.3, Part 1, Section 9.3.2 13 | 14 | 15 | ### `identification.keywords` 16 | 17 | WMO support includes a `wmo` keywords section (WMO Core Metadata Profile 1.3, Part 2, Table 16). Keyword requirements are the same as pygeometa's default keywords rules. 18 | 19 | 20 | ## Validation 21 | 22 | WMO Core Metadata Profile output can be validated using the [WMO Core Metadata Profile Test Suite](https://github.com/wmo-im/pywcmp). 23 | -------------------------------------------------------------------------------- /docs/content/reference/extensions/wmo-wcmp2.md: -------------------------------------------------------------------------------- 1 | # WMO Core Metadata Profile 2 (WCMP2) Schema Reference 2 | 3 | This document describes [WMO Core Metadata Profile (WCMP2)](https://wmo-im.github.io/wcmp2/standard/wcmp2-STABLE.html) schema 4 | extensions in pygeometa. 5 | 6 | ## Sections 7 | 8 | ### `identification` 9 | 10 | Property Name|Mandatory/Optional|Description|Example|Reference 11 | -------------|------------------|-----------|-------|---------: 12 | wmo_data_policy|Mandatory|WMO data policy as per Resolution 1 (Cg-Ext(2021) (`core` or `recommended`)|`core`|WMO Core Metadata Profile 2, clause 7 13 | 14 | ### `identification.keywords` 15 | 16 | pygeometa WCMP2 support includes the following `keywords` sections. 17 | 18 | * `earth-system-discipline`: [Earth system categories](https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline) as defined by the [WMO Unified Data Policy, Resolution 1 (Cg-Ext(2021), Annex 1](https://library.wmo.int/records/item/58009-wmo-unified-data-policy). 19 | 20 | Ensure that `vocabulary.url` is set to https://codes.wmo.int/wis/topic-hierarchy/earth-system-discipline 21 | 22 | ## Validation 23 | 24 | WMO Core Metadata Profile output can be validated using the [pywcmp](https://github.com/wmo-im/pywcmp) tool. 25 | -------------------------------------------------------------------------------- /docs/content/reference/extensions/wmo-wigos.md: -------------------------------------------------------------------------------- 1 | # WMO WIGOS Metadata Standard Reference 2 | 3 | This document describes [WMO WIGOS Metadata Standard](https://library.wmo.int/opac/doc_num.php?explnum_id=3653) schema 4 | extensions in pygeometa. 5 | 6 | ## Codes 7 | 8 | Codes for WMO WIGOS are available at https://codes.wmo.int/wmdr 9 | 10 | ## Sections 11 | 12 | ### `metadata` 13 | 14 | See MCF reference 15 | 16 | ### `contact` 17 | 18 | See MCF reference. WMO WIGOS MCF add the contact type `facility` to 19 | attach contact information to a facility. The `pointOfContact` MCF contact is attached 20 | to the `wmdr:Header` element. 21 | 22 | ### `identification` 23 | 24 | See MCF reference 25 | 26 | ### `facility` 27 | 28 | The `facility` object consists of 1..n keys. Key names are up to the user 29 | with key objects having the model below. 30 | 31 | Property Name|Mandatory/Optional|Description|Example|Reference 32 | -------------|------------------|-----------|-------|---------: 33 | identifier|Mandatory|WMO WIGOS identifier|0-20008-0-JFJ|WIGOS Metadata Representation, Section 8.6.4 34 | name|Mandatory|||WIGOS Metadata Representation, Section 4.3 35 | type|Mandatory|The type of the observing facility from the Station/platform type codelist (https://codes.wmo.int/wmdr/_FacilityType)|landFixed|WIGOS Metadata Representation, Section 4.3.2 36 | geopositioning_method|Optional|Element describes the geospatial reference system used for the specified geolocation (codelist https://codes.wmo.int/wmdr/_GeopositioningMethod)|gps|WIGOS Metadata Representation, Section 4.2.2 37 | url|Optional|An online resource containing additional information about the facility or equipment|https://example.org/station/123|WIGOS Metadata Representation, Section 4.2.2 38 | date_established|Mandatory|Date at which the observingFacility was established. Normally considered to be the date the first observations were made|2011-11-11T11:11:11Z|WIGOS Metadata Representation, Section 4.3.2 39 | wmo_region|Mandatory|The WMO region the observing facility is located in, from the WMORegionType codelist (https://codes.wmo.int/wmdr/_WMORegion)|northCentralAmericaCaribbean|WIGOS Metadata Representation, Section 4.3.2 40 | 41 | #### `territory` 42 | 43 | The `territory` object is a child of the `facility` object and 44 | allows for specifying 1..n child objects to model changing territory names 45 | over time. At least one child object is required. 46 | 47 | Property Name|Mandatory/Optional|Description|Example|Reference 48 | -------------|------------------|-----------|-------|---------: 49 | name|Mandatory|The territory the observing facility is located in, from the TerritoryType codelist (https://codes.wmo.int/wmdr/_TerritoryName)|`CAN`|WIGOS Metadata Representation, Section 4.3.2 50 | valid_period|Optional|Specifies at least the begin date of the indicated territoryName. If omitted, the dateEstablished of the facility will be assumed|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 4.3.2 51 | 52 | #### `spatiotemporal` 53 | 54 | The `spatiotemporal` object is a child of the `facility` object and 55 | allows for specifying 1..n child objects to model a moving location 56 | over time. At least one child object is required. 57 | 58 | Property Name|Mandatory/Optional|Description|Example|Reference 59 | -------------|------------------|-----------|-------|---------: 60 | timeperiod|Mandatory|Specifies at least the begin date accompanying the location|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 7.9 61 | location|Mandatory. The location property includes a `crs` property (EPSG code), and `point` property (x,y,z)|Representative or conventional geospatial location of observing facility, the reference location. This will always be a point location, but this location can change with time. |`crs: 4326, point: -75,45,400`, `end: now`|WIGOS Metadata Representation, Section 7.9 62 | 63 | #### `program_affiliation` 64 | The `program_affiliation` object is a child of the `facility` object and 65 | allows for specifying 1..n child objects to model program affiliations. 66 | 67 | Property Name|Mandatory/Optional|Description|Example|Reference 68 | -------------|------------------|-----------|-------|---------: 69 | program|Mandatory|Program Affiliation, see https://codes.wmo.int/wmdr/_ProgramAffiliation|`GOS`|WIGOS Metadata Representation, Section 4.3.2 70 | 71 | #### `reporting_status` 72 | The `reporting_status` object is a child of the `program_affiliation` object and 73 | allows for specifying 1..n child objects to model program affiliations reporting status 74 | over time. 75 | 76 | Property Name|Mandatory/Optional|Description|Example|Reference 77 | -------------|------------------|-----------|-------|---------: 78 | status|Mandatory|Declared reporting status of the observing facility from the ReportingStatusType codelist (https://codes.wmo.int/wmdr/_ReportingStatus)|`operational`| 79 | valid_period|Optional|Specifies at least the begin date of the indicated reportingStatus.|`begin: 2011-11-11`, `end: now`| 80 | 81 | #### `climate_zone` 82 | The `climate_zone` object is a child of the `facility` object and 83 | allows for specifying 0..n child objects to model changing climate zones over time. 84 | 85 | Property Name|Mandatory/Optional|Description|Example|Reference 86 | -------------|------------------|-----------|-------|---------: 87 | name|Mandatory|Climate zone of the observing facility, from the ClimateZone codelist (https://codes.wmo.int/wmdr/_ClimateZone)|`snowFullyHumidCoolSummer`|WIGOS Metadata Representation, Section 4.3.2 88 | valid_period|Optional|Specifies at least the begin date of the indicated climate zone. If omitted, the dateEstablished of the facility will be assumed|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 4.3.2 89 | 90 | #### `surface_cover` 91 | The `surface_cover` object is a child of the `facility` object and 92 | allows for specifying 0..n child objects to model changing surface covers over time. 93 | 94 | Property Name|Mandatory/Optional|Description|Example|Reference 95 | -------------|------------------|-----------|-------|---------: 96 | name|Mandatory|Predominant surface cover, from the given surface cover classification scheme and the SurfaceCover codelist (https://codes.wmo.int/wmdr/_SurfaceCover)|`rainfedCroplands`|WIGOS Metadata Representation, Section 4.3.2 97 | surface_cover_classification|Mandatory|Surface cover classification scheme, from the SurfaceCoverClassification codelist (https://codes.wmo.int/wmdr/_SurfaceCoverClassification)|`globCover2009`|WIGOS Metadata Representation, Section 4.3.2 98 | valid_period|Optional|Specifies at least the begin date. If omitted, the dateEstablished of the facility will be assumed|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 4.3.2 99 | 100 | #### `surface_roughness` 101 | The `surface_roughness` object is a child of the `facility` object and 102 | allows for specifying 0..n child objects. 103 | 104 | Property Name|Mandatory/Optional|Description|Example|Reference 105 | -------------|------------------|-----------|-------|---------: 106 | name|Mandatory|Surface roughness of surrounding of the observing facility, from the SurfaceRoughness codelist (https://codes.wmo.int/wmdr/_SurfaceRoughness)|`rough`|WIGOS Metadata Representation, Section 4.3.2 107 | valid_period|Optional|Specifies at least the begin date of the indicated surface roughness. If omitted, the dateEstablished of the facility will be assumed|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 4.3.2 108 | 109 | #### `topography_bathymetry` 110 | The `topography_bathymetry` object is a child of the `facility` object and 111 | allows for specifying 0..n child objects to model topography or bathymetry descriptions over time. 112 | 113 | Property Name|Mandatory/Optional|Description|Example|Reference 114 | -------------|------------------|-----------|-------|---------: 115 | local_topography|Optional|Local topography of the observing facility from the LocalTopography codelist (https://codes.wmo.int/wmdr/_LocalTopography)|`flat`|WIGOS Metadata Representation, Section 4.3.2 116 | relative_elevation|Optional|Relative elevation of the observing facility compared to its surrounding, from the RelativeElevation codelist (https://codes.wmo.int/wmdr/_RelativeElevation)|`inapplicable`|WIGOS Metadata Representation, Section 4.3.2 117 | topographic_context|Optional|Topographic context of the observing facility, from the TopographicContext codelist (https://codes.wmo.int/wmdr/_TopographicContext)|`plains`|WIGOS Metadata Representation, Section 4.3.2 118 | altitude_or_depth|Optional|Altitude or depth of observing facility, from the AltitudeOrDepth codelist (https://codes.wmo.int/wmdr/_AltitudeOrDepth)|`middleAltitude`|WIGOS Metadata Representation, Section 4.3.2 119 | valid_period|Optional|Specifies at least the begin date. If omitted, the dateEstablished of the facility will be assumed|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 4.3.2 120 | 121 | #### `observations` 122 | The `observations` object is a child of the `facility` object and 123 | allows for specifying 0..n child objects to model observations provided by a facility. 124 | 125 | Property Name|Mandatory/Optional|Description|Example|Reference 126 | -------------|------------------|-----------|-------|---------: 127 | name|mandatory|Freeform name of observed property|`Total column ozone`|WIGOS Metadata Representation, Section 4.3.2 128 | timeperiod|Optional|The time period over which the property is observed.|`begin: 2011-11-11`, `end: now`|WIGOS Metadata Representation, Section 6.2.5 129 | url|mandatory|The online resource of the final result (output) of the observation|https://example.org/data/atmos/spectral/total-column-ozone/111|WIGOS Metadata Representation, Section 6.2.5 130 | observedproperty|Mandatory|The property type being observed (`ObservingMethodAtmosphere`, `ObservingMethodTerrestrial`, `ObservedVariableAtmosphere`, `ObservedVariableEarth`, `ObservedVariableOcean`, `ObservedVariableOuterSpace`, `ObservedVariableTerrestrial`) and name (see WMO code lists) relevant to the type||`type: ObservingMethodAtmosphere`, `name: 263`|WIGOS Metadata Representation, Section 6.2.5 131 | -------------------------------------------------------------------------------- /docs/content/reference/index.md: -------------------------------------------------------------------------------- 1 | # Reference 2 | 3 | * [Metadata Control File](mcf.md) 4 | * Formats 5 | * [iso19139](extensions/iso19139-2.md), [specification](https://www.iso.org/standard/57104.html) 6 | * [iso19139-hnap](extensions/iso19139-hnap.md), [specification](https://www.gcpedia.gc.ca/wiki/Federal_Geospatial_Platform/Policies_and_Standards/Catalogue/Release/Appendix_B_Guidelines_and_Best_Practices/Guide_to_Harmonized_ISO_19115:2003_NAP) 7 | * [wmo-cmp](extensions/wmo-cmp.md), [specification](https://wis.wmo.int/2013/metadata/version_1-3-0/WMO_Core_Metadata_Profile_v1.3_Part_1.pdf) 8 | * [wmo-wcmp2](extensions/wmo-wcmp2.md), [specification](https://wmo-im.github.io/wcmp2/standard/wcmp2-STABLE.html) 9 | * [wmo-wigos](extensions/wmo-wigos.md), [specification](https://library.wmo.int/opac/doc_num.php?explnum_id=3653) 10 | -------------------------------------------------------------------------------- /docs/content/tutorial.md: -------------------------------------------------------------------------------- 1 | # pygeometa Tutorial 2 | 3 | ## Overview 4 | 5 | This tutorial provides a tour of pygeometa for both users and developers, 6 | and is aimed at getting you up and running quickly. Let's go! 7 | 8 | ## For Users 9 | 10 | ### Installation 11 | 12 | You require Python 3 or greater to use pygeometa. 13 | 14 | The easiest way to install pygeometa is using pip: 15 | 16 | ```bash 17 | pip3 install pygeometa 18 | ``` 19 | 20 | This will install the latest stable release. If you are looking to work with 21 | pygeometa from source, see the [For Developers](#for-developers) section for 22 | more information. 23 | 24 | ### Workflow 25 | 26 | The basic pygeometa workflow is: 27 | 28 | 1. Create a 'Metadata Control File' YAML file that contains metadata information 29 | 1. Modify the [sample.yml](https://github.com/geopython/pygeometa/blob/master/sample.yml) example 30 | 2. pygeometa supports nesting MCFs together, allowing providing a single MCF 31 | for common metadata parameters (e.g. common contact information) 32 | 3. Refer to the [Metadata Control File Reference documentation](https://geopython.github.io/pygeometa/reference/mcf) 33 | 3. Run pygeometa for the .yml file with a specified target metadata schema 34 | 35 | ### Running 36 | 37 | ```bash 38 | # show all subcommands 39 | pygeometa 40 | 41 | # show all supported schemas 42 | pygeometa metadata schemas 43 | 44 | # provide a basic sanity check/report on an MCF (Metadata Control File) 45 | pygeometa metadata info path/to/file.yml 46 | 47 | # generate an ISO 19139 document to stdout 48 | pygeometa metadata generate path/to/file.yml --schema=iso19139 49 | 50 | # generate an ISO 19139 document to disk 51 | pygeometa metadata generate path/to/file.yml --schema=iso19139 --output=some_file.xml 52 | 53 | # generate an ISO 19139 document to disk with debugging (ERROR, WARNING, INFO, DEBUG) 54 | pygeometa metadata generate path/to/file.yml --schema=iso19139 --output=some_file.xml --verbosity=DEBUG # add verbose (ERROR, WARNING, INFO, DEBUG) 55 | 56 | # use your own defined schema 57 | pygeometa metadata generate path/to/file.yml --schema_local=/path/to/my-schema --output=some_file.xml # to file 58 | 59 | # validate an MCF document 60 | pygeometa validate path/to/file.yml 61 | 62 | # import a metadata document to MCF 63 | pygeometa metadata import path/to/file.xml --schema=iso19139 64 | 65 | # import a metadata document to MCF, autodetecting the metadata file format 66 | pygeometa metadata import path/to/file.xml --schema=autodetect # --schema=autodetect is default 67 | 68 | # transform from one metadata representation to another 69 | pygeometa metadata transform path/to/file.xml --input-schema=iso19139 --output-schema=oarec-record 70 | 71 | # transform from one metadata representation to another, autodetecting the metadata file format 72 | pygeometa metadata transform path/to/file.xml --input-schema=autodetect --output-schema=oarec-record # --input-schema=autodetect is default 73 | ``` 74 | 75 | ## For Developers 76 | 77 | ### Installation 78 | 79 | pygeometa is best installed and used within a Python virtualenv. 80 | 81 | #### Requirements 82 | 83 | * Python 3 and above 84 | * Python [virtualenv](https://virtualenv.pypa.io/) package 85 | 86 | #### Dependencies 87 | 88 | Dependencies are listed in `requirements.txt`. Dependencies 89 | are automatically installed during pygeometa's installation. 90 | 91 | #### Installing the Package from Source 92 | 93 | ```bash 94 | python3 -m venv my-env 95 | cd my-env 96 | . bin/activate 97 | git clone https://github.com/geopython/pygeometa.git 98 | cd pygeometa 99 | python3 setup.py build 100 | python3 setup.py install 101 | ``` 102 | 103 | ### Using the API from Python 104 | 105 | ```python 106 | from pygeometa.core import read_mcf, render_j2_template 107 | 108 | # read from disk 109 | mcf_dict = read_mcf('/path/to/file.yml') 110 | # read from string 111 | mcf_dict = read_mcf(mcf_string) 112 | 113 | # choose ISO 19139 output schema 114 | from pygeometa.schemas.iso19139 import ISO19139OutputSchema 115 | iso_os = ISO19139OutputSchema() 116 | 117 | # default schema 118 | xml_string = iso_os.write(mcf_dict) 119 | 120 | # user-defined schema 121 | xml_string = render_j2_template(mcf_dict, schema_local='/path/to/new-schema') 122 | 123 | # write to disk 124 | with open('output.xml', 'wb') as ff: 125 | ff.write(xml_string) 126 | ``` 127 | 128 | ## Development 129 | 130 | ### Setting up a Development Environment 131 | 132 | Same as installing a package. Use a virtualenv. Also install developer 133 | requirements: 134 | 135 | ```bash 136 | pip3 install -r requirements-dev.txt 137 | ``` 138 | 139 | ### Adding a Metadata Schema to the Core 140 | 141 | Adding metadata schemas to pygeometa involves extending 142 | `pygeometa.schemas.base.BaseOutputSchema` and implementing the following design pattern: 143 | 144 | - `__init__`: initializer, including the following code: 145 | ```python 146 | # initialize args: 147 | super().__init__('shortname', 'my cool metadata', 'xml', THISDIR) 148 | # - 'shortname': shortname identifier for metadata schema 149 | # - 'my cool metadata': descripts of metadata schema 150 | # - 'xml': encoding type (default is `xml`; `json` also supported) 151 | # - 'THISDIR': current directory of plugin file for template rendering 152 | ``` 153 | - `write`: write a string or dictionary of metadata output. Default behaviour 154 | consists of Jinja2 template rendering (see [Jinja2 templates](#jinja2-templates) 155 | for more information). Outputs can be generated via other means (lxml, xml.tree, 156 | json, etc.) 157 | - `import_` (optional): import a metadata format into MCF 158 | 159 | Once you have added your metadata schema plugin, it needs to be registered it with 160 | pygeometa's schema registry: 161 | 162 | ```bash 163 | vi pygeometa/schemas/__init__.py 164 | # edit the SCHEMAS dict with the metadata schema name and dotted path of class 165 | ``` 166 | 167 | #### Jinja2 templates 168 | 169 | To add support for a new metadata schema using Jinja2 templates: 170 | 171 | ```bash 172 | cp -r pygeometa/schemas/iso19139 pygeometa/schemas/new-schema 173 | ``` 174 | 175 | Then modify `*.j2` files in the new `pygeometa/schemas/new-schema` directory 176 | to comply to new metadata schema. 177 | 178 | #### Custom tooling 179 | 180 | To add support for a new metadata schemas using other tooling/workflow: 181 | ```bash 182 | mkdir pygeometa/schemas/foo 183 | cp pygeometa/schemas/iso19139/__init__.py pygeometa/schemas/foo 184 | vi pygeometa/schemas/foo/__init__.py 185 | # update class name and super().__init__() function accordingly 186 | ``` 187 | 188 | ### Running Tests 189 | 190 | ```bash 191 | # via distutils 192 | python3 setup.py test 193 | # manually 194 | cd tests 195 | python3 run_tests.py 196 | ``` 197 | 198 | ## Releasing 199 | 200 | ```bash 201 | python3 setup.py sdist bdist_wheel --universal 202 | twine upload dist/* 203 | ``` 204 | 205 | ### Code Conventions 206 | 207 | * [PEP8](https://www.python.org/dev/peps/pep-0008) 208 | 209 | ### Bugs and Issues 210 | 211 | All bugs, enhancements and issues are managed on [GitHub](https://github.com/geopython/pygeometa/issues). 212 | 213 | ## History 214 | 215 | Started in 2009, pygeometa originated within an internal project called pygdm, 216 | which provided generic geospatial data management functions. pygdm (now end 217 | of life) was used for generating MSC/CMC geospatial metadata. pygeometa was 218 | pulled out of pygdm to focus on the core requirement of generating geospatial 219 | metadata within a real-time environment and automated workflows. 220 | 221 | In 2015 pygeometa was made publically available in support of the Treasury 222 | Board [Policy on Acceptable Network and Device Use](https://www.tbs-sct.gc.ca/pol/doc-eng.aspx?id=27122). 223 | -------------------------------------------------------------------------------- /docs/mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: "pygeometa: Metadata Creation for the Rest of Us" 2 | site_description: pygeometa is a Python package to generate metadata for geospatial datasets 3 | site_author: the pygeometa team 4 | copyright: Copyright (c) 2015 - 2025 the pygeometa team 5 | site_url: https://geopython.github.io/pygeometa 6 | repo_url: https://github.com/geopython/pygeometa 7 | docs_dir: content 8 | 9 | nav: 10 | # - Home: index.md 11 | - Code: https://github.com/geopython/pygeometa 12 | - Tutorial: tutorial.md 13 | - Reference: 14 | - Metadata Control File: 15 | - Core: reference/mcf.md 16 | - Extensions: 17 | - "ISO 19115-2, Part 2: Extensions for acquisition and processing": reference/extensions/iso19139-2.md 18 | - ISO Harmonized North American Profile (HNAP): reference/extensions/iso19139-hnap.md 19 | - WMO Core Metadata Profile: reference/extensions/wmo-cmp.md 20 | - WMO Core Metadata Profile 2: reference/extensions/wmo-wcmp2.md 21 | - WMO WIGOS Metadata Standard: reference/extensions/wmo-wigos.md 22 | # - Index: reference/index.md 23 | - pygeoapi plugin: pygeoapi-plugin.md 24 | 25 | #theme: slate 26 | 27 | edit_uri: edit/master/docs/content 28 | 29 | plugins: 30 | - search 31 | -------------------------------------------------------------------------------- /pygeometa/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2016 Government of Canada 22 | # Copyright (c) 2024 Tom Kralidis 23 | # 24 | # Permission is hereby granted, free of charge, to any person 25 | # obtaining a copy of this software and associated documentation 26 | # files (the "Software"), to deal in the Software without 27 | # restriction, including without limitation the rights to use, 28 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | # copies of the Software, and to permit persons to whom the 30 | # Software is furnished to do so, subject to the following 31 | # conditions: 32 | # 33 | # The above copyright notice and this permission notice shall be 34 | # included in all copies or substantial portions of the Software. 35 | # 36 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 38 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 41 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 42 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 43 | # OTHER DEALINGS IN THE SOFTWARE. 44 | # 45 | # ================================================================= 46 | 47 | import click 48 | 49 | from pygeometa.core import (generate, import_, info, schemas, 50 | transform, validate) 51 | 52 | __version__ = '0.17.dev1' 53 | 54 | 55 | @click.group() 56 | @click.version_option(version=__version__) 57 | def cli(): 58 | pass 59 | 60 | 61 | @click.group() 62 | def metadata(): 63 | """Metadata management""" 64 | pass 65 | 66 | 67 | metadata.add_command(generate) 68 | metadata.add_command(import_) 69 | metadata.add_command(info) 70 | metadata.add_command(schemas) 71 | metadata.add_command(transform) 72 | metadata.add_command(validate) 73 | cli.add_command(metadata) 74 | -------------------------------------------------------------------------------- /pygeometa/cli_options.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2016 Government of Canada 22 | # Copyright (c) 2022 Tom Kralidis 23 | # 24 | # Permission is hereby granted, free of charge, to any person 25 | # obtaining a copy of this software and associated documentation 26 | # files (the "Software"), to deal in the Software without 27 | # restriction, including without limitation the rights to use, 28 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | # copies of the Software, and to permit persons to whom the 30 | # Software is furnished to do so, subject to the following 31 | # conditions: 32 | # 33 | # The above copyright notice and this permission notice shall be 34 | # included in all copies or substantial portions of the Software. 35 | # 36 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 38 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 41 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 42 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 43 | # OTHER DEALINGS IN THE SOFTWARE. 44 | # 45 | # ================================================================= 46 | 47 | import logging 48 | import sys 49 | 50 | import click 51 | 52 | ARGUMENT_MCF = click.argument('mcf') 53 | ARGUMENT_METADATA_FILE = click.argument('metadata-file', type=click.File()) 54 | 55 | OPTION_OUTPUT = click.option( 56 | '--output', 57 | '-o', 58 | type=click.File('w', encoding='utf-8'), 59 | help='Name of output file') 60 | 61 | 62 | def OPTION_VERBOSITY(f): 63 | logging_options = ['ERROR', 'WARNING', 'INFO', 'DEBUG'] 64 | 65 | def callback(ctx, param, value): 66 | if value is not None: 67 | logging.basicConfig(stream=sys.stdout, 68 | level=getattr(logging, value)) 69 | return True 70 | 71 | return click.option('--verbosity', '-v', 72 | type=click.Choice(logging_options), 73 | help='Verbosity', 74 | callback=callback)(f) 75 | 76 | 77 | def cli_callbacks(f): 78 | f = OPTION_VERBOSITY(f) 79 | return f 80 | -------------------------------------------------------------------------------- /pygeometa/helpers.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2024 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import base64 47 | from datetime import date, datetime, time 48 | from decimal import Decimal 49 | import json 50 | import logging 51 | from pathlib import Path 52 | from typing import Any 53 | 54 | LOGGER = logging.getLogger(__name__) 55 | 56 | THISDIR = Path(__file__).resolve().parent 57 | 58 | 59 | def json_dumps(obj) -> str: 60 | """ 61 | Helper function to dump dict to JSON string 62 | 63 | :param obj: `dict` of JSON 64 | 65 | :returns: `str` of JSON 66 | """ 67 | 68 | return json.dumps(obj, default=json_serial, indent=4, ensure_ascii=False) 69 | 70 | 71 | def json_serial(obj) -> Any: 72 | """ 73 | Helper function to convert to JSON non-default 74 | types (source: https://stackoverflow.com/a/22238613) 75 | 76 | :param obj: `object` to be evaluated 77 | 78 | :returns: JSON non-default type to `str` 79 | """ 80 | 81 | if isinstance(obj, (datetime, date, time)): 82 | return obj.isoformat() 83 | elif isinstance(obj, bytes): 84 | try: 85 | LOGGER.debug('Returning as UTF-8 decoded bytes') 86 | return obj.decode('utf-8') 87 | except UnicodeDecodeError: 88 | LOGGER.debug('Returning as base64 encoded JSON object') 89 | return base64.b64encode(obj) 90 | elif isinstance(obj, Decimal): 91 | return float(obj) 92 | 93 | msg = f'{obj} type {type(obj)} not serializable' 94 | LOGGER.error(msg) 95 | raise TypeError(msg) 96 | -------------------------------------------------------------------------------- /pygeometa/schemas/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2022 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import importlib 47 | import logging 48 | import os 49 | 50 | from pygeometa.schemas.base import BaseOutputSchema 51 | 52 | LOGGER = logging.getLogger(__name__) 53 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 54 | 55 | SCHEMAS = { 56 | 'iso19139': 'pygeometa.schemas.iso19139.ISO19139OutputSchema', 57 | 'iso19139-2': 'pygeometa.schemas.iso19139_2.ISO19139_2OutputSchema', 58 | 'iso19139-hnap': 'pygeometa.schemas.iso19139_hnap.ISO19139HNAPOutputSchema', # noqa 59 | 'oarec-record': 'pygeometa.schemas.ogcapi_records.OGCAPIRecordOutputSchema', # noqa 60 | 'stac-item': 'pygeometa.schemas.stac.STACItemOutputSchema', 61 | 'dcat': 'pygeometa.schemas.dcat.DCATOutputSchema', 62 | 'wmo-cmp': 'pygeometa.schemas.wmo_cmp.WMOCMPOutputSchema', 63 | 'wmo-wcmp2': 'pygeometa.schemas.wmo_wcmp2.WMOWCMP2OutputSchema', 64 | 'wmo-wigos': 'pygeometa.schemas.wmo_wigos.WMOWIGOSOutputSchema' 65 | } 66 | 67 | 68 | def get_supported_schemas(details: bool = False, 69 | include_autodetect: bool = False) -> list: 70 | """ 71 | Get supported schemas 72 | 73 | :param details: provide read/write details 74 | :param include_autodetect: include magic auto detection mode 75 | 76 | :returns: list of supported schemas 77 | """ 78 | 79 | def has_mode(plugin: BaseOutputSchema, mode: str) -> bool: 80 | enabled = False 81 | 82 | try: 83 | _ = getattr(plugin, mode)('test') 84 | except NotImplementedError: 85 | pass 86 | except Exception: 87 | enabled = True 88 | 89 | return enabled 90 | 91 | schema_matrix = [] 92 | 93 | LOGGER.debug('Generating list of supported schemas') 94 | 95 | if not details: 96 | if include_autodetect: 97 | schemas_keys = list(SCHEMAS.keys()) 98 | schemas_keys.append('autodetect') 99 | return schemas_keys 100 | else: 101 | return SCHEMAS.keys() 102 | 103 | for key in SCHEMAS.keys(): 104 | schema = load_schema(key) 105 | can_read = has_mode(schema, 'import_') 106 | can_write = has_mode(schema, 'write') 107 | 108 | schema_matrix.append({ 109 | 'id': key, 110 | 'description': schema.description, 111 | 'read': can_read, 112 | 'write': can_write 113 | }) 114 | 115 | if include_autodetect: 116 | schema_matrix.append({ 117 | 'id': 'autodetect', 118 | 'description': 'Auto schema detection', 119 | 'read': True, 120 | 'write': False 121 | }) 122 | 123 | return schema_matrix 124 | 125 | 126 | def load_schema(schema_name: str) -> BaseOutputSchema: 127 | """ 128 | loads schema plugin by name 129 | 130 | :param schema_name: shortname of schema 131 | 132 | :returns: plugin object 133 | """ 134 | 135 | LOGGER.debug(f'Schemas: {SCHEMAS.keys()}') 136 | 137 | if schema_name not in SCHEMAS.keys(): 138 | msg = f'Schema {schema_name} not found' 139 | LOGGER.exception(msg) 140 | raise InvalidSchemaError(msg) 141 | 142 | name = SCHEMAS[schema_name] 143 | 144 | if '.' in name: # dotted path 145 | packagename, classname = name.rsplit('.', 1) 146 | else: 147 | raise InvalidSchemaError(f'Schema path {name} not found') 148 | 149 | LOGGER.debug(f'package name: {packagename}') 150 | LOGGER.debug(f'class name: {classname}') 151 | 152 | module = importlib.import_module(packagename) 153 | class_ = getattr(module, classname) 154 | 155 | return class_() 156 | 157 | 158 | class InvalidSchemaError(Exception): 159 | """Invalid plugin""" 160 | pass 161 | -------------------------------------------------------------------------------- /pygeometa/schemas/base.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2022 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | from typing import Union 48 | 49 | from pygeometa import core 50 | 51 | TEMPLATES = os.path.dirname(os.path.realpath(__file__)) 52 | 53 | 54 | class BaseOutputSchema: 55 | """generic OutputSchema ABC""" 56 | 57 | def __init__(self, name: str = None, description: str = None, 58 | outputformat: str = None, template_dir: str = None): 59 | """ 60 | Initialize object 61 | 62 | :param name: name of output schema 63 | :param description: description of output schema 64 | :param outputformat: output format (XML, JSON) 65 | 66 | :returns: pygeometa.schemas.base.BaseOutputSchema 67 | """ 68 | 69 | self.name = name 70 | self.description = description 71 | self.outputformat = outputformat 72 | self.template_dir = template_dir 73 | 74 | def write(self, mcf: dict, stringify: str = True) -> Union[dict, str]: 75 | """ 76 | Write outputschema to string buffer 77 | 78 | :param mcf: dict of MCF content model 79 | :param stringify: whether to return a string representation (default) 80 | else native (dict, etree) 81 | 82 | :returns: `dict` or `str` of metadata in outputschema representation 83 | """ 84 | 85 | if stringify: 86 | return core.render_j2_template(mcf, template_dir=self.template_dir) 87 | 88 | return mcf 89 | 90 | def import_(self, metadata: str) -> dict: 91 | """ 92 | Import metadata into MCF 93 | 94 | :param metadata: string of metadata content 95 | 96 | :returns: `dict` of MCF content 97 | """ 98 | 99 | raise NotImplementedError() 100 | 101 | def __repr__(self): 102 | return f'<{self.name.upper()}OutputSchema> {self.name}' 103 | -------------------------------------------------------------------------------- /pygeometa/schemas/common/iso19139-charstring.j2: -------------------------------------------------------------------------------- 1 | {% macro get_freetext(element, language_alternate, myvars) -%} 2 | {% if language_alternate is none or myvars[1] is none %} 3 | {% if myvars[0]|trim != "None" %} 4 | 5 | {{ myvars[0]|trim|e }} 6 | 7 | {% endif %} 8 | {% else %} 9 | {% if myvars[0]|trim != "None" %} 10 | 11 | {{ myvars[0]|trim|e }} 12 | 13 | 14 | {{ myvars[1]|trim|e }} 15 | 16 | 17 | 18 | {% endif %} 19 | {% endif %} 20 | {% endmacro %} 21 | -------------------------------------------------------------------------------- /pygeometa/schemas/dcat/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2024 Tom Kralidis, Paul van Genuchten 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | from typing import Union 48 | 49 | from pygeometa.helpers import json_dumps 50 | from pygeometa.schemas.base import BaseOutputSchema 51 | 52 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 53 | 54 | 55 | class DCATOutputSchema(BaseOutputSchema): 56 | """dcat output schema""" 57 | 58 | def __init__(self): 59 | """ 60 | Initialize object 61 | 62 | :returns: pygeometa.schemas.base.BaseOutputSchema 63 | """ 64 | 65 | description = 'DCAT' 66 | super().__init__('dcat', description, 'json', THISDIR) 67 | 68 | def write(self, mcf: dict, stringify: str = True) -> Union[dict, str]: 69 | """ 70 | Write MCF to DCAT 71 | 72 | :param mcf: dict of MCF content model 73 | :param stringify: whether to return a string representation (default) 74 | else native (dict, etree) 75 | 76 | :returns: `dict` or `str` of MCF as a DCAT representation 77 | """ 78 | 79 | dcat = { 80 | "@context": { 81 | # namespaces 82 | "adms": "http://www.w3.org/ns/adms#", 83 | "dcat": "http://www.w3.org/ns/dcat#", 84 | "dct": "http://purl.org/dc/terms/", 85 | "foaf": "http://xmlns.com/foaf/0.1/", 86 | "gsp": "http://www.opengis.net/ont/geosparql#", 87 | "locn": "http://www.w3.org/ns/locn#", 88 | "owl": "http://www.w3.org/2002/07/owl#", 89 | "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", 90 | "rdfs": "http://www.w3.org/2000/01/rdf-schema#", 91 | "schema": "http://schema.org/", 92 | "skos": "http://www.w3.org/2004/02/skos/core#", 93 | "time": "http://www.w3.org/2006/time", 94 | "vcard": "http://www.w3.org/2006/vcard/ns#", 95 | "xsd": "http://www.w3.org/2001/XMLSchema#", 96 | # mcf-property to dcat mappings 97 | "topiccategory": "dcat:theme", 98 | "language": "dct:language", 99 | # todo: support any range of languages from source 100 | "title": "dct:title", 101 | "title_en": {"@id": "dct:title", "@language": "en"}, 102 | "title_fr": {"@id": "dct:title", "@language": "fr"}, 103 | "abstract_en": {"@id": "dct:description", "@language": "en"}, 104 | "abstract_fr": {"@id": "dct:description", "@language": "fr"}, 105 | "distribution": "dcat:distribution", 106 | "url": "dcat:accessURL", 107 | "name": "dct:title", 108 | "name_en": {"@id": "dct:title", "@language": "en"}, 109 | "name_fr": {"@id": "dct:title", "@language": "fr"}, 110 | "description": "dct:description", 111 | "description_en": {"@id": "dct:description", 112 | "@language": "en"}, 113 | "description_fr": {"@id": "dct:description", 114 | "@language": "fr"}, 115 | "keywords": "dct:keyword", 116 | "keywords_en": {"@id": "dct:keyword", "@language": "en"}, 117 | "keywords_fr": {"@id": "dct:keyword", "@language": "fr"}, 118 | "dataseturi": {"@type": "@id", "@id": "@id"}, 119 | "contact": "dcat:contactPoint", 120 | "spatial": "dct:spatial", 121 | "temporal": "dct:temporal", 122 | "creation": "dct:issued", 123 | "modified": "dct:modified", 124 | "maintenancefrequency": "dct:accrualPeriodicity", 125 | "type": "dcat:mediaType", 126 | "size": "dcat:byteSize", 127 | "status": "adms:status", 128 | "organization": "vcard:hasOrganizationName", 129 | "individualname": "vcard:fn", 130 | "phone": "vcard:hasTelephone", 131 | "address": "vcard:street-address", 132 | "city": "vcard:locality", 133 | "postalcode": "vcard:postal-code", 134 | "country": "vcard:country-name", 135 | "email": "vcard:hasEmail", 136 | "accessconstraints": "dct:accessRights", 137 | "rights": "dct:rights", 138 | "rights_en": {"@id": "dct:rights", "@language": "en"}, 139 | "rights_fr": {"@id": "dct:rights", "@language": "fr"}, 140 | "bbox": "dcat:bbox", 141 | "begin": "dcat:startDate", 142 | "end": "dcat:endDate" 143 | }, 144 | "@type": "dcat:Dataset", 145 | "keywords": [], 146 | "keywords_en": [], 147 | "keywords_fr": [], 148 | "distribution": [], 149 | "contact": [] 150 | } 151 | 152 | # prepare mcf for json-ld 153 | for key, value in mcf.items(): 154 | # do nothing for these items (yet) 155 | if (key in ['mcf', 'content_info', 'acquisition']): 156 | None 157 | # unnest these items 158 | elif (key in ['metadata', 'identification']): 159 | for k, v in value.items(): 160 | if (k == 'extents'): 161 | for k1, v1 in v.items(): 162 | # assign dct:location type 163 | if (k1 == 'spatial'): 164 | dcat["spatial"] = [] 165 | for k2 in v1: 166 | k2['@type'] = 'dct:Location' 167 | dcat["spatial"].append(k2) 168 | # assign dct:PeriodOftime type 169 | elif (k1 == 'temporal'): 170 | dcat['temporal'] = [] 171 | for k3 in v1: 172 | k3['@type'] = 'dct:PeriodOfTime' 173 | dcat["temporal"].append(k3) 174 | # unnest keywords 175 | elif (k == 'keywords'): 176 | for k4, v4 in v.items(): 177 | for k5, v5 in v4.items(): 178 | if (k5 != 'keywords_type'): 179 | for kw in v5: 180 | # assumes a key for language exists 181 | if k5 in dcat: 182 | dcat[k5].append(kw) 183 | elif (k in ['identifier']): 184 | # mint a url from identifier if non exists on mcf 185 | if (not mcf['metadata']['dataseturi']): 186 | dcat['dataseturi'] = 'http://example.com/#' + \ 187 | str(v) 188 | elif (k in ['dates']): 189 | for k6, v6 in v.items(): 190 | dcat[k6] = v6 191 | else: 192 | dcat[k] = v 193 | # transform set of keys to array 194 | elif (key in ['distributor', 'contact']): 195 | for k, v in value.items(): 196 | # add id (if url exists) 197 | if (not isinstance(v, str) and v['url']): 198 | v['@id'] = v['url'] 199 | # add type 200 | if (key == 'distribution'): 201 | v['@type'] = 'dcat:Distribution' 202 | else: 203 | v['@type'] = 'vcard:Organization' 204 | dcat[key].append(v) 205 | # other cases: root properties 206 | else: 207 | dcat[key] = value 208 | 209 | if stringify: 210 | return json_dumps(dcat) 211 | 212 | return dcat 213 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2022 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import ast 47 | import logging 48 | import os 49 | 50 | from lxml import etree 51 | from owslib.iso import CI_OnlineResource, CI_ResponsibleParty, MD_Metadata 52 | 53 | from pygeometa.schemas.base import BaseOutputSchema 54 | 55 | LOGGER = logging.getLogger(__name__) 56 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 57 | 58 | 59 | class ISO19139OutputSchema(BaseOutputSchema): 60 | """ISO 19139 output schema""" 61 | 62 | def __init__(self): 63 | """ 64 | Initialize object 65 | 66 | :returns: pygeometa.schemas.base.BaseOutputSchema 67 | """ 68 | 69 | description = 'ISO 19115/19139' 70 | 71 | super().__init__('iso19139', description, 'xml', THISDIR) 72 | 73 | def import_(self, metadata: str) -> dict: 74 | """ 75 | Import metadata into MCF 76 | 77 | :param metadata: string of metadata content 78 | 79 | :returns: `dict` of MCF content 80 | """ 81 | 82 | mcf = { 83 | 'mcf': { 84 | 'version': '1.0', 85 | }, 86 | 'metadata': {}, 87 | 'identification': {}, 88 | 'contact': {}, 89 | 'distribution': {} 90 | } 91 | 92 | LOGGER.debug('Parsing ISO metadata') 93 | try: 94 | m = MD_Metadata(etree.fromstring(metadata)) 95 | except ValueError: 96 | m = MD_Metadata(etree.fromstring(bytes(metadata, 'utf-8'))) 97 | 98 | LOGGER.debug('Setting metadata') 99 | mcf['metadata']['identifier'] = m.identifier 100 | 101 | mcf['metadata']['hierarchylevel'] = m.hierarchy 102 | mcf['metadata']['datestamp'] = m.datestamp 103 | 104 | LOGGER.debug('Setting language') 105 | if m.language: 106 | mcf['metadata']['language'] = m.language 107 | elif m.languagecode: 108 | mcf['metadata']['language'] = m.languagecode 109 | 110 | identification = m.identification[0] 111 | 112 | LOGGER.debug('Setting identification') 113 | mcf['identification']['title'] = identification.title 114 | mcf['identification']['abstract'] = identification.abstract 115 | 116 | if identification.date: 117 | mcf['identification']['dates'] = {} 118 | for date_ in identification.date: 119 | mcf['identification']['dates'][date_.type] = date_.date 120 | 121 | if identification.keywords: 122 | mcf['identification']['keywords'] = {} 123 | for count, value in enumerate(identification.keywords): 124 | key = f'keywords-{count}' 125 | mcf['identification']['keywords'][key] = { 126 | 'keywords_type': value.type, 127 | 'keywords': [k.name for k in value.keywords] 128 | } 129 | if value.thesaurus is not None: 130 | mcf['identification']['keywords'][key]['vocabulary'] = { 131 | 'name': value.thesaurus['title'], 132 | 'url': value.thesaurus['url'] 133 | } 134 | 135 | mcf['identification']['topiccategory'] = identification.topiccategory # noqa 136 | 137 | mcf['identification']['extents'] = { 138 | 'spatial': [{ 139 | 'bbox': [ 140 | ast.literal_eval(identification.extent.boundingBox.minx), 141 | ast.literal_eval(identification.extent.boundingBox.miny), 142 | ast.literal_eval(identification.extent.boundingBox.maxx), 143 | ast.literal_eval(identification.extent.boundingBox.maxy) 144 | ] 145 | }], 146 | 'temporal': [] 147 | } 148 | 149 | temp_extent = { 150 | 'begin': None, 151 | 'end': None 152 | } 153 | 154 | if identification.temporalextent_start: 155 | temp_extent['begin'] = identification.temporalextent_start 156 | if identification.temporalextent_end: 157 | temp_extent['end'] = identification.temporalextent_end 158 | 159 | mcf['identification']['extents']['temporal'].append(temp_extent) 160 | 161 | if identification.accessconstraints: 162 | mcf['identification']['accessconstraints'] = identification.accessconstraints[0] # noqa 163 | 164 | mcf['identification']['status'] = identification.status 165 | 166 | LOGGER.debug('Setting contacts') 167 | # for contact in m.get_all_contacts(): 168 | # mcf['contact'].update(get_contact(contact)) 169 | mcf['contact'].update(get_contact(m.contact[0])) 170 | 171 | LOGGER.debug('Setting distribution') 172 | if m.distribution: 173 | for count, value in enumerate(m.distribution.online): 174 | key = f'link-{count}' 175 | mcf['distribution'][key] = get_link(value) 176 | 177 | return mcf 178 | 179 | 180 | def get_contact(contact: CI_ResponsibleParty) -> dict: 181 | """ 182 | Generates an MCF contact from an OWSLib contact 183 | 184 | :param contact: OWSLib `CI_ResponsibleParty` object 185 | 186 | :returns: dict of MCF contact 187 | """ 188 | 189 | mcf_contact = {contact.role: {}} 190 | 191 | cm_lookup = { 192 | 'name': 'name', 193 | 'organization': 'organization', 194 | 'positionname': 'position', 195 | 'phone': 'phone', 196 | 'fax': 'fax', 197 | 'address': 'address', 198 | 'city': 'city', 199 | 'administrativearea': 'region', 200 | 'postalcode': 'postcode', 201 | 'country': 'country', 202 | 'email': 'email' 203 | } 204 | 205 | for key, value in cm_lookup.items(): 206 | if getattr(contact, value) is not None: 207 | mcf_contact[contact.role][key] = getattr(contact, value) 208 | 209 | if hasattr(contact.onlineresource, 'url'): 210 | mcf_contact[contact.role]['url'] = contact.onlineresource.url 211 | 212 | return mcf_contact 213 | 214 | 215 | def get_link(link: CI_OnlineResource) -> dict: 216 | """ 217 | Generates an MCF link from an OWSLib distribution URL 218 | 219 | :param contact: OWSLib `CI_OnlineResource` object 220 | 221 | :returns: dict of MCF link 222 | """ 223 | 224 | mcf_link = { 225 | 'url': link.url, 226 | 'type': link.protocol, 227 | 'name': link.name, 228 | 'description': link.description, 229 | 'function': link.function 230 | } 231 | 232 | return mcf_link 233 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139/contact.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ cs.get_freetext('individualName', record['metadata']['language_alternate'], get_charstring(contact.get('individualname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 3 | {{ cs.get_freetext('organisationName', record['metadata']['language_alternate'], get_charstring(contact.get('organization'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 4 | {{ cs.get_freetext('positionName', record['metadata']['language_alternate'], get_charstring(contact.get('positionname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 5 | 6 | 7 | 8 | 9 | {% if contact['phone'] %} 10 | 11 | {{ contact['phone'] }} 12 | 13 | {% else %} 14 | 15 | {% endif %} 16 | {% if contact['fax'] %} 17 | 18 | {{ contact['fax'] }} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | {{ cs.get_freetext('deliveryPoint', record['metadata']['language_alternate'], get_charstring(contact.get('address'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 28 | {{ cs.get_freetext('city', record['metadata']['language_alternate'], get_charstring(contact.get('city'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 29 | {{ cs.get_freetext('administrativeArea', record['metadata']['language_alternate'], get_charstring(contact.get('administrativearea'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 30 | 31 | {{ contact['postalcode'] }} 32 | 33 | {{ cs.get_freetext('country', record['metadata']['language_alternate'], get_charstring(contact.get('country'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 34 | {{ cs.get_freetext('electronicMailAddress', record['metadata']['language_alternate'], get_charstring(contact.get('email'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 35 | 36 | 37 | 38 | 39 | 40 | {{ contact['url']|e }} 41 | 42 | 43 | WWW:LINK 44 | 45 | 46 | information 47 | 48 | 49 | 50 | {{ cs.get_freetext('hoursOfService', record['metadata']['language_alternate'], get_charstring(contact.get('hoursofservice'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 51 | {{ cs.get_freetext('contactInstructions', record['metadata']['language_alternate'], get_charstring(contact.get('contactinstructions'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 52 | 53 | 54 | 55 | {{ role }} 56 | 57 | 58 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139_2/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2020 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | 48 | from pygeometa.schemas.base import BaseOutputSchema 49 | 50 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 51 | 52 | 53 | class ISO19139_2OutputSchema(BaseOutputSchema): 54 | """ISO 19139-2 output schema""" 55 | 56 | def __init__(self): 57 | """ 58 | Initialize object 59 | 60 | :returns: pygeometa.schemas.base.BaseOutputSchema 61 | """ 62 | 63 | description = 'ISO 19115-2/19139-2' 64 | super().__init__('iso19139-2', description, 'xml', THISDIR) 65 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139_2/contact.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ cs.get_freetext('individualName', record['metadata']['language_alternate'], get_charstring(contact.get('individualname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 3 | {{ cs.get_freetext('organisationName', record['metadata']['language_alternate'], get_charstring(contact.get('organization'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 4 | {{ cs.get_freetext('positionName', record['metadata']['language_alternate'], get_charstring(contact.get('positionname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 5 | 6 | 7 | 8 | 9 | {% if contact['phone'] %} 10 | 11 | {{ contact['phone'] }} 12 | 13 | {% else %} 14 | 15 | {% endif %} 16 | {% if contact['fax'] %} 17 | 18 | {{ contact['fax'] }} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | {{ cs.get_freetext('deliveryPoint', record['metadata']['language_alternate'], get_charstring(contact.get('address'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 28 | {{ cs.get_freetext('city', record['metadata']['language_alternate'], get_charstring(contact.get('city'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 29 | {{ cs.get_freetext('administrativeArea', record['metadata']['language_alternate'], get_charstring(contact.get('administrativearea'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 30 | 31 | {{ contact['postalcode'] }} 32 | 33 | {{ cs.get_freetext('country', record['metadata']['language_alternate'], get_charstring(contact.get('country'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 34 | {{ cs.get_freetext('electronicMailAddress', record['metadata']['language_alternate'], get_charstring(contact.get('email'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 35 | 36 | 37 | 38 | 39 | 40 | {{ contact['url']|e }} 41 | 42 | 43 | WWW:LINK 44 | 45 | 46 | information 47 | 48 | 49 | 50 | {{ cs.get_freetext('hoursOfService', record['metadata']['language_alternate'], get_charstring(contact.get('hoursofservice'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 51 | {{ cs.get_freetext('contactInstructions', record['metadata']['language_alternate'], get_charstring(contact.get('contactinstructions'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 52 | 53 | 54 | 55 | {{ role }} 56 | 57 | 58 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139_hnap/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2020 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | 48 | from pygeometa.schemas.base import BaseOutputSchema 49 | 50 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 51 | 52 | 53 | class ISO19139HNAPOutputSchema(BaseOutputSchema): 54 | """ISO 19139 HNAP output schema""" 55 | 56 | def __init__(self): 57 | """ 58 | Initialize object 59 | 60 | :returns: pygeometa.schemas.base.BaseOutputSchema 61 | """ 62 | 63 | description = 'ISO 19139 HNAP' 64 | 65 | super().__init__('iso19139-hnap', description, 'xml', THISDIR) 66 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139_hnap/charstring.j2: -------------------------------------------------------------------------------- 1 | {% macro get_freetext(element, language_alternate, myvars) -%} 2 | {% if language_alternate is none or myvars[1] is none %} 3 | {% if myvars[0]|trim != "None" %} 4 | 5 | {{ myvars[0]|trim|e }} 6 | 7 | {% endif %} 8 | {% else %} 9 | {% if myvars[0]|trim != "None" %} 10 | 11 | {{ myvars[0]|trim|e }} 12 | 13 | 14 | {{ myvars[1]|trim|e }} 15 | 16 | 17 | 18 | {% endif %} 19 | {% endif %} 20 | {% endmacro %} 21 | -------------------------------------------------------------------------------- /pygeometa/schemas/iso19139_hnap/contact.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ cs.get_freetext('individualName', 'fra', get_charstring(contact.get('individualname'), 'en', 'fr')) }} 3 | {{ cs.get_freetext('organisationName', 'fra', get_charstring(contact.get('organization'), 'en', 'fr')) }} 4 | {{ cs.get_freetext('positionName', 'fra', get_charstring(contact.get('positionname'), 'en', 'fr')) }} 5 | 6 | 7 | 8 | 9 | {% if contact['phone'] %} 10 | 11 | {{ contact['phone'] }} 12 | 13 | {% else %} 14 | 15 | {% endif %} 16 | {% if contact['fax'] %} 17 | 18 | {{ contact['fax'] }} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | {{ cs.get_freetext('deliveryPoint', 'fra', get_charstring(contact.get('address'), 'en', 'fr')) }} 28 | {{ cs.get_freetext('city', 'fra', get_charstring(contact.get('city'), 'en', 'fr')) }} 29 | {{ cs.get_freetext('administrativeArea', 'fra', get_charstring(contact.get('administrativearea'), 'en', 'fr')) }} 30 | 31 | {{ contact['postalcode'] }} 32 | 33 | {{ cs.get_freetext('country', 'fra', get_charstring(contact.get('country'), 'en', 'fr')) }} 34 | 35 | {{ contact['email'] }} 36 | 37 | 38 | {{ contact['email'] }} 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | {{ contact['url']|e }} 48 | 49 | 50 | WWW:LINK 51 | 52 | 53 | information 54 | 55 | 56 | 57 | {{ cs.get_freetext('hoursOfService', 'fra', get_charstring(contact.get('hoursofservice'), 'en', 'fr')) }} 58 | {{ cs.get_freetext('contactInstructions', 'fra', get_charstring(contact.get('contactinstructions'), 'en', 'fr')) }} 59 | 60 | 61 | 62 | {{ role}} 63 | 64 | 65 | -------------------------------------------------------------------------------- /pygeometa/schemas/mcf/iso19139_2.yaml: -------------------------------------------------------------------------------- 1 | $schema: https://json-schema.org/draft/2020-12/schema 2 | $id: https://github.com/geopython/pygeometa/blob/master/pygeometa/schemas/mcf/iso19139_2.yaml 3 | title: pygeometa ISO 19139-2 configuration schema 4 | description: pygeometa ISO 19139-2 configuration schema 5 | 6 | 7 | allOf: 8 | - $ref: './core.yaml' 9 | properties: 10 | acquisition: 11 | type: object 12 | properties: 13 | platforms: 14 | type: object 15 | properties: 16 | identifier: 17 | type: string 18 | description: unique identification of the platform 19 | description: 20 | type: string 21 | description: platform description 22 | instruments: 23 | type: array 24 | items: 25 | type: object 26 | properties: 27 | identifier: 28 | type: string 29 | description: instrument identifier 30 | type: 31 | type: string 32 | description: instrument type 33 | required: 34 | - identifier 35 | - description 36 | - instruments 37 | required: 38 | - platforms 39 | -------------------------------------------------------------------------------- /pygeometa/schemas/mcf/wmo-cmp.yaml: -------------------------------------------------------------------------------- 1 | $schema: https://json-schema.org/draft/2020-12/schema 2 | $id: https://github.com/geopython/pygeometa/blob/master/pygeometa/schemas/mcf/wmo-cmp.yaml 3 | title: pygeometa WMO Core Metadata Profile configuration schema 4 | description: pygeometa WMO Core Metadata Profile configuration schema 5 | 6 | 7 | allOf: 8 | - $ref: './core.yaml#/properties/identification' 9 | properties: 10 | otherconstraints_wmo_data_policy: 11 | type: string 12 | description: WMO data policy statment from WMO_DataLicenseCode 13 | enum: 14 | - WMOEssential 15 | - WMOAdditional 16 | - WMOOther 17 | otherconstraints_wmo_gts_priority: 18 | type: string 19 | description: WMO GTS priority 20 | enum: 21 | - GTSPriority1 22 | - GTSPriority2 23 | - GTSPriority3 24 | - GTSPriority4 25 | -------------------------------------------------------------------------------- /pygeometa/schemas/mcf/wmo-wcmp2.yaml: -------------------------------------------------------------------------------- 1 | $schema: https://json-schema.org/draft/2020-12/schema 2 | $id: https://github.com/geopython/pygeometa/blob/master/pygeometa/schemas/mcf/wmo-wcmp2.yaml 3 | title: pygeometa WMO Core Metadata Profile 2 configuration schema 4 | description: pygeometa WMO Core Metadata Profile 2 configuration schema 5 | 6 | 7 | allOf: 8 | - $ref: './core.yaml#/properties/identification' 9 | properties: 10 | wmo_data_policy: 11 | type: string 12 | description: Identifies the classification of the dataset exchange as described by WMO Unified Data Policy for the International Exchange of Earth System Data (Resolution 1 (Cg-Ext(2021) [32]. 13 | enum: 14 | - core 15 | - recommended 16 | -------------------------------------------------------------------------------- /pygeometa/schemas/mcf/wmo-wigos.yaml: -------------------------------------------------------------------------------- 1 | $schema: https://json-schema.org/draft/2020-12/schema 2 | $id: https://github.com/geopython/pygeometa/blob/master/pygeometa/schemas/mcf/wmo-wigos.yaml 3 | title: pygeometa WMO WIGOS Metadata Standard configuration schema 4 | description: pygeometa WMO WIGOS Metadata Standard configuration schema 5 | 6 | 7 | allOf: 8 | - $ref: './core.yaml' 9 | properties: 10 | facility: 11 | patternProperties: 12 | "^.*": 13 | type: object 14 | description: |- 15 | The user defined key name indicates a given facility. The values are the properties 16 | and objects. 17 | properties: 18 | identifier: 19 | type: string 20 | description: WMO WIGOS identifier 21 | name: 22 | type: string 23 | description: facility name 24 | type: 25 | type: string 26 | description: |- 27 | The type of the observing facility from the station/platform type 28 | codelist (https://codes.wmo.int/wmdr/_FacilityType) 29 | geopositioning_method: 30 | type: string 31 | description: |- 32 | Element describes the geospatial reference system used for the specified 33 | geolocation (codelist http://codes.wmo.int/wmdr/_GeopositioningMethod) 34 | url: 35 | type: string 36 | description: An online resource containing additional information about the facility or equipment 37 | date_established: 38 | type: string 39 | description: |- 40 | Date at which the observingFacility was established. Normally considered to be the date 41 | the first observations were made 42 | wmo_region:: 43 | type: string 44 | description: |- 45 | The WMO region the observing facility is located in, from the WMORegionType 46 | codelist (http://codes.wmo.int/wmdr/_WMORegion) 47 | territory: 48 | type: array 49 | items: 50 | type: object 51 | properties: 52 | name: 53 | type: string 54 | description: |- 55 | The territory the observing facility is located in, from the TerritoryType 56 | codelist (http://codes.wmo.int/wmdr/_TerritoryName) 57 | valid_period: 58 | type: object 59 | properties: 60 | begin: 61 | $ref: './core.yaml#/definitions/date_or_datetime_string' 62 | end: 63 | $ref: './core.yaml#/definitions/date_or_datetime_string' 64 | required: 65 | - begin 66 | required: 67 | - name 68 | spatiotemporal: 69 | type: array 70 | items: 71 | type: object 72 | properties: 73 | timeperiod: 74 | type: object 75 | properties: 76 | begin: 77 | $ref: './core.yaml#/definitions/date_or_datetime_string' 78 | end: 79 | $ref: './core.yaml#/definitions/date_or_datetime_string' 80 | required: 81 | - begin 82 | location: 83 | type: object 84 | properties: 85 | geomtype: 86 | type: string 87 | description: geometry type 88 | enum: 89 | - point 90 | - polygon 91 | default: point 92 | crs: 93 | type: number 94 | description: coordinate reference system 95 | default: 4326 96 | point: 97 | type: object 98 | properties: 99 | x: 100 | type: number 101 | description: x coordinate 102 | y: 103 | type: number 104 | description: y coordinate 105 | z: 106 | type: number 107 | description: z coordinate 108 | required: 109 | - x 110 | - y 111 | required: 112 | - geomtype 113 | - crs 114 | - point 115 | required: 116 | - timeperiod 117 | - location 118 | program_affilation: 119 | type: object 120 | properties: 121 | program: 122 | type: string 123 | description: Program Affiliation, see http://codes.wmo.int/wmdr/_ProgramAffiliation 124 | required: 125 | - program 126 | reporting_status: 127 | type: object 128 | properties: 129 | status: 130 | type: string 131 | description: Declared reporting status of the observing facility from the ReportingStatusType codelist (http://codes.wmo.int/wmdr/_ReportingStatus) 132 | valid_period: 133 | type: object 134 | description: Specifies at least the begin date of the indicated reportingStatus. 135 | properties: 136 | begin: 137 | $ref: './core.yaml#/definitions/date_or_datetime_string' 138 | end: 139 | $ref: './core.yaml#/definitions/date_or_datetime_string' 140 | required: 141 | - begin 142 | location: 143 | required: 144 | - status 145 | climate_zone: 146 | type: object 147 | properties: 148 | name: 149 | type: string 150 | description: Climate zone of the observing facility, from the ClimateZone codelist (http://codes.wmo.int/wmdr/_ClimateZone) 151 | valid_period: 152 | type: object 153 | description: Specifies at least the begin date of the indicated climate zone. If omitted, the dateEstablished of the facility will be assumed 154 | properties: 155 | begin: 156 | $ref: './core.yaml#/definitions/date_or_datetime_string' 157 | end: 158 | $ref: './core.yaml#/definitions/date_or_datetime_string' 159 | required: 160 | - begin 161 | location: 162 | required: 163 | - name 164 | surface_cover: 165 | type: object 166 | properties: 167 | name: 168 | type: string 169 | description: Predominant surface cover, from the given surface cover classification scheme and the SurfaceCover codelist (http://codes.wmo.int/wmdr/_SurfaceCover) 170 | surface_cover_classification: 171 | type: string 172 | description: Surface cover classification scheme, from the SurfaceCoverClassification codelist (http://codes.wmo.int/wmdr/_SurfaceCoverClassification) 173 | valid_period: 174 | type: object 175 | description: Specifies at least the begin date of the indicated climate zone. If omitted, the dateEstablished of the facility will be assumed 176 | properties: 177 | begin: 178 | $ref: './core.yaml#/definitions/date_or_datetime_string' 179 | end: 180 | $ref: './core.yaml#/definitions/date_or_datetime_string' 181 | required: 182 | - begin 183 | location: 184 | required: 185 | - name 186 | - surface_cover_classification 187 | surface_roughness: 188 | type: object 189 | properties: 190 | name: 191 | type: string 192 | description: Surface roughness of surrounding of the observing facility, from the SurfaceRoughness codelist (http://codes.wmo.int/wmdr/_SurfaceRoughness) 193 | valid_period: 194 | type: object 195 | description: Specifies at least the begin date of the indicated surface roughness. If omitted, the dateEstablished of the facility will be assumed 196 | properties: 197 | begin: 198 | $ref: './core.yaml#/definitions/date_or_datetime_string' 199 | end: 200 | $ref: './core.yaml#/definitions/date_or_datetime_string' 201 | required: 202 | - begin 203 | location: 204 | required: 205 | - name 206 | topography_bathymetry: 207 | type: object 208 | properties: 209 | local_topography: 210 | type: string 211 | description: Local topography of the observing facility from the LocalTopography codelist (http://codes.wmo.int/wmdr/_LocalTopography) 212 | relative_elevation: 213 | type: string 214 | description: Relative elevation of the observing facility compared to its surrounding, from the RelativeElevation codelist (http://codes.wmo.int/wmdr/_RelativeElevation) 215 | topographic_context: 216 | type: string 217 | description: Topographic context of the observing facility, from the TopographicContext codelist (http://codes.wmo.int/wmdr/_TopographicContext) 218 | altitude_or_depth: 219 | type: string 220 | description: Altitude or depth of observing facility, from the AltitudeOrDepth codelist (http://codes.wmo.int/wmdr/_AltitudeOrDepth) 221 | valid_period: 222 | type: object 223 | description: Specifies at least the begin date of the indicated surface roughness. If omitted, the dateEstablished of the facility will be assumed 224 | properties: 225 | begin: 226 | $ref: './core.yaml#/definitions/date_or_datetime_string' 227 | end: 228 | $ref: './core.yaml#/definitions/date_or_datetime_string' 229 | required: 230 | - begin 231 | required: 232 | - name 233 | observations: 234 | properties: 235 | name: 236 | type: string 237 | description: Freeform name of observed property 238 | timeperiod: 239 | type: object 240 | description: The time period over which the property is observed. 241 | properties: 242 | begin: 243 | $ref: './core.yaml#/definitions/date_or_datetime_string' 244 | end: 245 | $ref: './core.yaml#/definitions/date_or_datetime_string' 246 | required: 247 | url: 248 | type: string 249 | description: The online resource of the final result (output) of the observation 250 | observedproperty: 251 | type: object 252 | properties: 253 | name: 254 | type: string 255 | description: name relevant to the type 256 | type: 257 | type: string 258 | description: The property type being observed (ObservingMethodAtmosphere, ObservingMethodTerrestrial, ObservedVariableAtmosphere, ObservedVariableEarth, ObservedVariableOcean, ObservedVariableOuterSpace, ObservedVariableTerrestrial) 259 | required: 260 | - name 261 | - url 262 | - observedproperty 263 | required: 264 | - identifier 265 | - name 266 | - type 267 | - date_established 268 | - wmo_region 269 | - program_affiliation 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /pygeometa/schemas/stac/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2024 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | from typing import Union 48 | 49 | from pygeometa.core import get_charstring 50 | from pygeometa.helpers import json_dumps 51 | from pygeometa.schemas.base import BaseOutputSchema 52 | 53 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 54 | 55 | 56 | class STACItemOutputSchema(BaseOutputSchema): 57 | """STAC Item output schema""" 58 | 59 | def __init__(self): 60 | """ 61 | Initialize object 62 | 63 | :returns: pygeometa.schemas.base.BaseOutputSchema 64 | """ 65 | 66 | description = 'STAC Item' 67 | 68 | super().__init__('stac-item', description, 'json', THISDIR) 69 | 70 | def write(self, mcf: dict, stringify: str = True) -> Union[dict, str]: 71 | """ 72 | Write MCF to STAC Item 73 | 74 | :param mcf: dict of MCF content model 75 | :param stringify: whether to return a string representation (default) 76 | else native (dict, etree) 77 | 78 | :returns: `dict` or `str` of MCF as a STAC item 79 | """ 80 | 81 | lang1 = mcf['metadata'].get('language') 82 | lang2 = mcf['metadata'].get('language_alternate') 83 | 84 | minx, miny, maxx, maxy = (mcf['identification']['extents'] 85 | ['spatial'][0]['bbox']) 86 | 87 | title = get_charstring(mcf['identification'].get('title'), 88 | lang1, lang2) 89 | description = get_charstring(mcf['identification'].get('abstract'), 90 | lang1, lang2) 91 | 92 | stac_item = { 93 | 'stac-version': '1.0.0-beta.2', 94 | 'id': mcf['metadata']['identifier'], 95 | 'type': 'Feature', 96 | 'bbox': [minx, miny, maxx, maxy], 97 | 'geometry': { 98 | 'type': 'Polygon', 99 | 'coordinates': [[ 100 | [minx, miny], 101 | [minx, maxy], 102 | [maxx, maxy], 103 | [maxx, miny], 104 | [minx, miny] 105 | ]] 106 | }, 107 | 'properties': { 108 | 'title': title[0], 109 | 'description': description[0], 110 | 'providers': [] 111 | }, 112 | 'links': [] 113 | } 114 | 115 | if 'temporal' in mcf['identification']['extents']: 116 | begin = mcf['identification']['extents']['temporal'][0]['begin'] 117 | end = mcf['identification']['extents']['temporal'][0]['end'] 118 | 119 | stac_item['properties']['start_datetime'] = begin 120 | stac_item['properties']['end_datetime'] = end 121 | 122 | if 'creation' in mcf['identification']['dates']: 123 | stac_item['properties']['created'] = mcf['identification']['dates']['creation'] # noqa 124 | if 'revision' in mcf['identification']['dates']: 125 | stac_item['properties']['updated'] = mcf['identification']['dates']['revision'] # noqa 126 | 127 | for value in mcf['contact'].values(): 128 | stac_item['properties']['providers'].append({ 129 | 'name': value['organization']}) 130 | 131 | for value in mcf['distribution'].values(): 132 | title = get_charstring(value.get('title'), lang1, lang2) 133 | link = { 134 | 'rel': value.get('rel') or value.get('function'), 135 | 'title': title, 136 | 'href': value['url'] 137 | } 138 | stac_item['links'].append(link) 139 | 140 | if stringify: 141 | return json_dumps(stac_item) 142 | 143 | return stac_item 144 | -------------------------------------------------------------------------------- /pygeometa/schemas/wmo_cmp/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2020 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | 48 | from pygeometa.schemas.base import BaseOutputSchema 49 | 50 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 51 | 52 | 53 | class WMOCMPOutputSchema(BaseOutputSchema): 54 | """WMO Core Metadata Profile output schema""" 55 | 56 | def __init__(self): 57 | """ 58 | Initialize object 59 | 60 | :returns: pygeometa.schemas.base.BaseOutputSchema 61 | """ 62 | 63 | description = 'WMO Core Metadata Profile (WCMP)' 64 | super().__init__('wmo-cmp', description, 'xml', THISDIR) 65 | -------------------------------------------------------------------------------- /pygeometa/schemas/wmo_cmp/contact.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ cs.get_freetext('individualName', record['metadata']['language_alternate'], get_charstring(contact.get('individualname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 3 | {{ cs.get_freetext('organisationName', record['metadata']['language_alternate'], get_charstring(contact.get('organization'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 4 | {{ cs.get_freetext('positionName', record['metadata']['language_alternate'], get_charstring(contact.get('positionname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 5 | 6 | 7 | 8 | 9 | {% if contact['phone'] %} 10 | 11 | {{ contact['phone'] }} 12 | 13 | {% else %} 14 | 15 | {% endif %} 16 | {% if contact['fax'] %} 17 | 18 | {{ contact['fax'] }} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | {{ cs.get_freetext('deliveryPoint', record['metadata']['language_alternate'], get_charstring(contact.get('address'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 28 | {{ cs.get_freetext('city', record['metadata']['language_alternate'], get_charstring(contact.get('city'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 29 | {{ cs.get_freetext('administrativeArea', record['metadata']['language_alternate'], get_charstring(contact.get('administrativearea'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 30 | 31 | {{ contact['postalcode'] }} 32 | 33 | {{ cs.get_freetext('country', record['metadata']['language_alternate'], get_charstring(contact.get('country'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 34 | {{ cs.get_freetext('electronicMailAddress', record['metadata']['language_alternate'], get_charstring(contact.get('email'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 35 | 36 | 37 | 38 | 39 | 40 | {{ contact['url']|e }} 41 | 42 | 43 | WWW:LINK 44 | 45 | 46 | information 47 | 48 | 49 | 50 | {{ cs.get_freetext('hoursOfService', record['metadata']['language_alternate'], get_charstring(contact.get('hoursofservice'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 51 | {{ cs.get_freetext('contactInstructions', record['metadata']['language_alternate'], get_charstring(contact.get('contactinstructions'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 52 | 53 | 54 | 55 | {{ role }} 56 | 57 | 58 | -------------------------------------------------------------------------------- /pygeometa/schemas/wmo_wcmp2/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2024 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | from datetime import datetime 47 | import logging 48 | import os 49 | from typing import Union 50 | 51 | from pygeometa.helpers import json_dumps 52 | from pygeometa.schemas.ogcapi_records import OGCAPIRecordOutputSchema 53 | 54 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 55 | 56 | LOGGER = logging.getLogger(__name__) 57 | 58 | 59 | class WMOWCMP2OutputSchema(OGCAPIRecordOutputSchema): 60 | """OGC API - Records - Part 1: Core record schema""" 61 | 62 | def __init__(self): 63 | """ 64 | Initialize object 65 | 66 | :returns: pygeometa.schemas.base.BaseOutputSchema 67 | """ 68 | 69 | description = 'WMO Core Metadata Profile 2 (WCMP2)' 70 | 71 | super().__init__() 72 | 73 | self.description = description 74 | 75 | def write(self, mcf: dict, stringify: str = True) -> Union[dict, str]: 76 | """ 77 | Write outputschema to JSON string buffer 78 | 79 | :param mcf: dict of MCF content model 80 | :param stringify: whether to return a string representation (default) 81 | else native (dict, etree) 82 | 83 | 84 | :returns: `dict` or `str` of MCF as an OARec record representation 85 | """ 86 | 87 | record = super().write(mcf, stringify=False) 88 | 89 | LOGGER.debug('Setting WCMP2 conformance') 90 | record['conformsTo'] = ['http://wis.wmo.int/spec/wcmp/2/conf/core'] 91 | 92 | if 'edition' in mcf['identification']: 93 | record['properties']['version'] = mcf['identification']['edition'] 94 | 95 | LOGGER.debug('Setting WCMP2 distribution links') 96 | record['links'] = [] 97 | for key, value in mcf['distribution'].items(): 98 | link = self.generate_link(value) 99 | 100 | record['links'].append(link) 101 | 102 | if mcf['metadata'].get('hierarchylevel') == 'dataset': 103 | try: 104 | record['properties']['wmo:dataPolicy'] = mcf['identification']['wmo_data_policy'] # noqa 105 | except KeyError: 106 | LOGGER.warning('Missing wmo:dataPolicy') 107 | 108 | if record['properties'].get('created') is None: 109 | record['properties']['created'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%SZ') # noqa 110 | 111 | if stringify: 112 | return json_dumps(record) 113 | else: 114 | return record 115 | -------------------------------------------------------------------------------- /pygeometa/schemas/wmo_wigos/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2020 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | 48 | from pygeometa.schemas.base import BaseOutputSchema 49 | 50 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 51 | 52 | 53 | class WMOWIGOSOutputSchema(BaseOutputSchema): 54 | """WMO WIGOS output schema""" 55 | 56 | def __init__(self): 57 | """ 58 | Initialize object 59 | 60 | :returns: pygeometa.schemas.base.BaseOutputSchema 61 | """ 62 | 63 | description = 'WMO WIGOS Metadata Standard' 64 | 65 | super().__init__('wmo-wigos', description, 'xml', THISDIR) 66 | -------------------------------------------------------------------------------- /pygeometa/schemas/wmo_wigos/contact.j2: -------------------------------------------------------------------------------- 1 | 2 | {{ cs.get_freetext('individualName', record['metadata']['language_alternate'], get_charstring(contact.get('individualname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 3 | {{ cs.get_freetext('organisationName', record['metadata']['language_alternate'], get_charstring(contact.get('organization'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 4 | {{ cs.get_freetext('positionName', record['metadata']['language_alternate'], get_charstring(contact.get('positionname'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 5 | 6 | 7 | 8 | 9 | {% if contact['phone'] %} 10 | 11 | {{ contact['phone'] }} 12 | 13 | {% else %} 14 | 15 | {% endif %} 16 | {% if contact['fax'] %} 17 | 18 | {{ contact['fax'] }} 19 | 20 | {% else %} 21 | 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | {{ cs.get_freetext('deliveryPoint', record['metadata']['language_alternate'], get_charstring(contact.get('address'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 28 | {{ cs.get_freetext('city', record['metadata']['language_alternate'], get_charstring(contact.get('city'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 29 | {{ cs.get_freetext('administrativeArea', record['metadata']['language_alternate'], get_charstring(contact.get('administrativearea'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 30 | 31 | {{ contact['postalcode'] }} 32 | 33 | {{ cs.get_freetext('country', record['metadata']['language_alternate'], get_charstring(contact.get('country'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 34 | {{ cs.get_freetext('electronicMailAddress', record['metadata']['language_alternate'], get_charstring(contact.get('email'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 35 | 36 | 37 | 38 | 39 | 40 | {{ contact['url']|e }} 41 | 42 | 43 | WWW:LINK 44 | 45 | 46 | information 47 | 48 | 49 | 50 | {{ cs.get_freetext('hoursOfService', record['metadata']['language_alternate'], get_charstring(contact.get('hoursofservice'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 51 | {{ cs.get_freetext('contactInstructions', record['metadata']['language_alternate'], get_charstring(contact.get('contactinstructions'), record['metadata']['language'], record['metadata']['language_alternate'])) }} 52 | 53 | 54 | 55 | {{ role }} 56 | 57 | 58 | -------------------------------------------------------------------------------- /pygeometa/schemas/wmo_wigos/main.j2: -------------------------------------------------------------------------------- 1 | 2 | {% import 'common/iso19139-charstring.j2' as cs %} 3 | 4 | 5 | 6 | 7 | {{ record['metadata']['datestamp'].strftime('%Y-%m-%dT%H:%M:%SZ') }} 8 | 9 | {% set contact = record['contact']['record_owner'] %} 10 | {% set contact_id = 'recordOwner' %} 11 | {% set role = 'pointOfContact' %} 12 | {% include "contact.j2" %} 13 | 14 | 15 | 16 | {% for k, v in record['facility'].items() %} 17 | 18 | 19 | http://wigos.wmo.int/{{ v['identifier'] }} 20 | {{ v['name'] }} 21 | 22 | 23 | 24 | {% set contact = record['contact']['facility'] %} 25 | {% set contact_id = 'responsibleParty' + v['identifier'] %} 26 | {% set role = 'pointOfContact' %} 27 | {% include "contact.j2" %} 28 | 29 | 30 | 31 | {% for gl in v['spatiotemporal'] %} 32 | 33 | 34 | {% set pos = gl['location']['point'].split(',') %} 35 | {% if pos == [''] %} 36 | 37 | {% else %} 38 | 39 | 40 | {{ pos[1] }} {{ pos[0] }} {{ pos[2] }} 41 | 42 | 43 | {% endif %} 44 | {% if v['geoposition_method'] %} 45 | 46 | {% endif %} 47 | 48 | 49 | {{ gl['timeperiod']['begin'] }} 50 | {% if gl['timeperiod']['end'] %} 51 | {{ gl['timeperiod']['end'] }} 52 | {% else %} 53 | 54 | {% endif %} 55 | 56 | 57 | 58 | 59 | {% endfor %} 60 | 61 | 62 | 63 | {{ v['url'] }} 64 | 65 | 66 | WWW:LINK 67 | 68 | 69 | information 70 | 71 | 72 | 73 | 74 | {% if v['date_established'] %} 75 | {{ v['date_established'] }} 76 | {% endif %} 77 | 78 | 79 | 80 | {% for t in v['territory'] %} 81 | 82 | {% if t['valid_period'] %} 83 | 84 | 85 | {{ t['valid_period']['begin'] }} 86 | {% if t['valid_period']['end'] %} 87 | {{ t['valid_period']['end'] }} 88 | {% else %} 89 | 90 | {% endif %} 91 | 92 | 93 | {% endif %} 94 | {% endfor %} 95 | 96 | 97 | {% for pa in v['program_affiliation'] %} 98 | {% set outer_loop = loop %} 99 | 100 | 101 | 102 | {% for rs in pa['reporting_status'] %} 103 | 104 | 105 | 106 | 107 | 108 | {{ rs['valid_period']['begin'] }} 109 | {% if rs['valid_period']['end'] %} 110 | {{ rs['valid_period']['end'] }} 111 | {% else %} 112 | 113 | {% endif %} 114 | 115 | 116 | 117 | 118 | {% endfor %} 119 | 120 | 121 | {% endfor %} 122 | {% for c in v['climate_zone'] %} 123 | 124 | 125 | 126 | 127 | 128 | {{ c['valid_period']['begin'] }} 129 | {% if c['valid_period']['end'] %} 130 | {{ c['valid_period']['end'] }} 131 | {% else %} 132 | 133 | {% endif %} 134 | 135 | 136 | 137 | 138 | {% endfor %} 139 | {% for c in v['surface_cover'] %} 140 | 141 | 142 | 143 | 144 | 145 | 146 | {{ c['valid_period']['begin'] }} 147 | {% if c['valid_period']['end'] %} 148 | {{ c['valid_period']['end'] }} 149 | {% else %} 150 | 151 | {% endif %} 152 | 153 | 154 | 155 | 156 | {% endfor %} 157 | {% for c in v['surface_roughness'] %} 158 | 159 | 160 | 161 | 162 | 163 | {{ c['valid_period']['begin'] }} 164 | {% if c['valid_period']['end'] %} 165 | {{ c['valid_period']['end'] }} 166 | {% else %} 167 | 168 | {% endif %} 169 | 170 | 171 | 172 | 173 | {% endfor %} 174 | {% for c in v['topography_bathymetry'] %} 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | {{ c['valid_period']['begin'] }} 184 | {% if c['valid_period']['end'] %} 185 | {{ c['valid_period']['end'] }} 186 | {% else %} 187 | 188 | {% endif %} 189 | 190 | 191 | 192 | 193 | {% endfor %} 194 | {% if v['observations'] %} 195 | {% for obs in v['observations'] %} 196 | 197 | 198 | 199 | 200 | 201 | 202 | {{ obs['name'] }} 203 | 204 | 205 | {{ obs['timeperiod']['begin'] }} 206 | {% if obs['timeperiod']['end'] %} 207 | {{ obs['timeperiod']['end'] }} 208 | {% else %} 209 | 210 | {% endif %} 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | {{ obs['url'] }} 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | {% endfor %} 242 | {% endif %} 243 | 244 | 245 | {% endfor %} 246 | 247 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | flake8 3 | mkdocs 4 | mkdocs-bootswatch 5 | twine 6 | wheel 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Click 2 | Jinja2 3 | jsonschema 4 | lxml 5 | OWSLib 6 | pyyaml 7 | -------------------------------------------------------------------------------- /sample-wmo-wigos.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: en 7 | language_alternate: fr 8 | charset: utf8 9 | datestamp: 2014-11-11T11:11:11Z 10 | 11 | contact: 12 | main: &contact_main 13 | organization: Environment Canada 14 | url: http://www.ec.gc.ca/ 15 | individualname: Tom Kralidis 16 | positionname: Senior Systems Scientist 17 | phone: +01-123-456-7890 18 | fax: +01-123-456-7890 19 | address: 4905 Dufferin Street 20 | city: Toronto 21 | administrativearea: Ontario 22 | postalcode: M3H 5T4 23 | country: Canada 24 | email: foo@bar.tld 25 | hoursofservice: 0700h - 1500h EST 26 | contactinstructions: email 27 | 28 | facility: *contact_main 29 | record_owner: *contact_main 30 | 31 | facility: 32 | first_station: 33 | identifier: 0-20000-0-123 34 | name: My station 35 | type: landFixed 36 | geopositioning_method: GPS 37 | url: http://example.org/facility/123 38 | spatiotemporal: 39 | - timeperiod: 40 | begin: 2011-11-11 41 | end: now # optional 42 | location: 43 | geomtype: point 44 | crs: 4326 45 | point: -75,45,400 46 | date_established: 1999-11-11 47 | program_affiliation: 48 | - program: 'WIGOSnonAffiliated' 49 | reporting_status: 50 | - valid_period: 51 | begin: 1999-11-11 52 | end: now # optional 53 | status: operational 54 | - program: 'GOS' 55 | reporting_status: 56 | - valid_period: 57 | begin: 1999-11-11 58 | end: 2018-08-02 59 | status: operational 60 | - valid_period: 61 | begin: 2018-08-02 62 | end: now # optional 63 | status: closed 64 | territory: 65 | - name: CAN 66 | valid_period: 67 | begin: 1999-11-11 68 | end: now # optional 69 | wmo_region: northCentralAmericaCaribbean 70 | climate_zone: 71 | - name: snowFullyHumidCoolSummer 72 | valid_period: 73 | begin: 1999-11-11 74 | end: 2000-11-11 75 | - name: snowFullyHumidWarmSummer 76 | valid_period: 77 | begin: 2000-11-11 78 | end: now # optional 79 | surface_cover: 80 | - name: rainfedCroplands 81 | surface_cover_classification: globCover2009 82 | valid_period: 83 | begin: 1999-11-11 84 | end: 2000-11-11 85 | - name: mosaicCroplands 86 | surface_cover_classification: globCover2009 87 | valid_period: 88 | begin: 2000-11-11 89 | end: now # optional 90 | surface_roughness: 91 | - name: rough 92 | valid_period: 93 | begin: 1999-11-11 94 | end: 2000-11-11 95 | - name: roughlyOpen 96 | valid_period: 97 | begin: 2000-11-11 98 | end: now # optional 99 | topography_bathymetry: 100 | - local_topography: unknown 101 | relative_elevation: inapplicable 102 | topographic_context: plains 103 | altitude_or_depth: middleAltitude 104 | valid_period: 105 | begin: 1999-11-11 106 | end: 2000-11-11 107 | - local_topography: flat 108 | relative_elevation: inapplicable 109 | topographic_context: plains 110 | altitude_or_depth: middleAltitude 111 | valid_period: 112 | begin: 2000-11-11 113 | end: now # optional 114 | observations: 115 | - name: my observation name 116 | timeperiod: 117 | begin: 2010-09-15 118 | end: 2014-03-31 119 | observedproperty: 120 | type: ObservedVariableAtmosphere 121 | name: 262 122 | url: https://example.org/data/atmos 123 | - name: Ozone/TotalOzone/dobson 124 | timeperiod: 125 | begin: 2013-01-01 126 | end: 2015-12-01 127 | observedproperty: 128 | type: ObservedVariableAtmosphere 129 | name: 263 130 | url: https://example.org/data/atmos/spectral/total-column-ozone/111 131 | -------------------------------------------------------------------------------- /sample.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: en 7 | language_alternate: fr 8 | charset: utf8 9 | parentidentifier: someparentid 10 | hierarchylevel: dataset 11 | datestamp: 2014-11-11 12 | dataseturi: http://some/minted/uri 13 | 14 | spatial: 15 | datatype: vector 16 | geomtype: point 17 | 18 | identification: 19 | language: eng; CAN 20 | charset: utf8 21 | title: 22 | en: title in English 23 | fr: title in French 24 | abstract: 25 | en: abstract in English 26 | fr: abstract in French 27 | edition: 1.8.0 28 | dates: 29 | creation: 2000-09-01T00:00:00Z 30 | publication: 2001-11-11 31 | keywords: 32 | default: 33 | keywords: 34 | en: [kw1 in English,kw2 in English,kw3 in English] 35 | fr: [kw1 in French,kw2 in French,kw3 in French] 36 | wmo: 37 | keywords: 38 | en: [FOO,BAR] 39 | keywords_type: theme 40 | vocabulary: 41 | name: 42 | en: My vocabulary 43 | fr: Mon vocabulaire 44 | url: http://example.org/vocab 45 | gc_cst: 46 | keywords: 47 | en: [kw1,kw2] 48 | fr: [kw1,kw2] 49 | topiccategory: 50 | - climatologyMeteorologyAtmosphere 51 | extents: 52 | spatial: 53 | - bbox: [-141,42,-52,84] 54 | crs: 4326 55 | temporal: 56 | - begin: 1950-07-31 57 | end: now 58 | resolution: P1Y 59 | fees: None 60 | accessconstraints: otherRestrictions 61 | license: 62 | name: CC BY 4.0 63 | url: https://creativecommons.org/licenses/by/4.0 64 | rights: 65 | en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 66 | fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 67 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 68 | status: onGoing 69 | maintenancefrequency: continual 70 | 71 | content_info: 72 | type: image 73 | cloud_cover: 72 74 | processing_level: "1.0" 75 | attributes: 76 | - name: foo 77 | units: m 78 | - name: bar 79 | units: K 80 | dimensions: 81 | - name: B1 82 | units: nm 83 | min: 932 84 | max: 958 85 | 86 | # platform metadata, applicable to iso19139-2 output 87 | acquisition: 88 | platforms: 89 | - identifier: LANDSAT_8 90 | description: Landsat 8 91 | instruments: 92 | - identifier: OLI_TIRS 93 | type: INS-NOBS 94 | 95 | contact: 96 | pointOfContact: &contact_poc 97 | organization: Environment Canada 98 | url: https://www.ec.gc.ca/ 99 | individualname: Tom Kralidis 100 | positionname: Senior Systems Scientist 101 | phone: +01-123-456-7890 102 | fax: +01-123-456-7890 103 | address: 4905 Dufferin Street 104 | city: Toronto 105 | administrativearea: Ontario 106 | postalcode: M3H 5T4 107 | country: Canada 108 | email: foo@bar.tld 109 | hoursofservice: 0700h - 1500h EST 110 | contactinstructions: email 111 | 112 | distributor: *contact_poc 113 | 114 | distribution: 115 | waf: 116 | url: https://example.org/data 117 | type: WWW:LINK 118 | rel: canonical 119 | name: my waf 120 | description: 121 | en: description in English 122 | fr: description in French 123 | function: download 124 | 125 | wms: 126 | url: https://example.org/wms 127 | type: OGC:WMS 128 | rel: service 129 | name: 130 | en: roads 131 | fr: routes 132 | description: 133 | en: description in English 134 | fr: description in French 135 | function: download 136 | 137 | dataquality: 138 | scope: 139 | level: dataset 140 | lineage: 141 | statement: this dataset was derived from a custom process against dataset xyz 142 | 143 | attributes: 144 | - name: foo 145 | units: m 146 | - name: bar 147 | units: K 148 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2016 Government of Canada 22 | # Copyright (c) 2025 Tom Kralidis 23 | # 24 | # Permission is hereby granted, free of charge, to any person 25 | # obtaining a copy of this software and associated documentation 26 | # files (the "Software"), to deal in the Software without 27 | # restriction, including without limitation the rights to use, 28 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 29 | # copies of the Software, and to permit persons to whom the 30 | # Software is furnished to do so, subject to the following 31 | # conditions: 32 | # 33 | # The above copyright notice and this permission notice shall be 34 | # included in all copies or substantial portions of the Software. 35 | # 36 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 37 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 38 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 39 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 41 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 42 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 43 | # OTHER DEALINGS IN THE SOFTWARE. 44 | # 45 | # ================================================================= 46 | 47 | from pathlib import Path 48 | from setuptools import Command, find_packages, setup 49 | import re 50 | import sys 51 | 52 | 53 | class PyTest(Command): 54 | user_options = [] 55 | 56 | def initialize_options(self): 57 | pass 58 | 59 | def finalize_options(self): 60 | pass 61 | 62 | def run(self): 63 | import subprocess 64 | errno = subprocess.call([sys.executable, 'tests/run_tests.py']) 65 | raise SystemExit(errno) 66 | 67 | 68 | def read(filename) -> str: 69 | """read file contents""" 70 | 71 | fullpath = Path(__file__).resolve().parent / filename 72 | 73 | with fullpath.open() as fh: 74 | contents = fh.read().strip() 75 | 76 | return contents 77 | 78 | 79 | def get_package_version(): 80 | """get version from top-level package init""" 81 | 82 | version_file = read('pygeometa/__init__.py') 83 | version_match = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", 84 | version_file, re.M) 85 | if version_match: 86 | return version_match.group(1) 87 | 88 | raise RuntimeError("Unable to find version string.") 89 | 90 | 91 | LONG_DESCRIPTION = read('README.md') 92 | 93 | DESCRIPTION = 'pygeometa is a Python package to generate metadata for geospatial datasets' # noqa 94 | 95 | MANIFEST = Path('MANIFEST') 96 | 97 | if MANIFEST.exists(): 98 | MANIFEST.unlink() 99 | 100 | setup( 101 | name='pygeometa', 102 | version=get_package_version(), 103 | description=DESCRIPTION.strip(), 104 | long_description=LONG_DESCRIPTION, 105 | long_description_content_type='text/markdown', 106 | license='MIT', 107 | platforms='all', 108 | keywords=' '.join([ 109 | 'geospatial', 110 | 'metadata', 111 | 'catalogue', 112 | 'discovery' 113 | ]), 114 | author='Tom Kralidis', 115 | author_email='tomkralidis@gmail.com', 116 | maintainer='Tom Kralidis', 117 | maintainer_email='tomkralidis@gmail.com', 118 | url='https://geopython.github.io/pygeometa', 119 | install_requires=read('requirements.txt').splitlines(), 120 | packages=find_packages(), 121 | include_package_data=True, 122 | entry_points={ 123 | 'console_scripts': [ 124 | 'pygeometa=pygeometa:cli' 125 | ] 126 | }, 127 | classifiers=[ 128 | 'Development Status :: 4 - Beta', 129 | 'Environment :: Console', 130 | 'Intended Audience :: Developers', 131 | 'Intended Audience :: Science/Research', 132 | 'License :: OSI Approved :: MIT License', 133 | 'Operating System :: OS Independent', 134 | 'Programming Language :: Python', 135 | 'Topic :: Scientific/Engineering :: GIS' 136 | ], 137 | cmdclass={'test': PyTest}, 138 | test_suite='tests.run_tests' 139 | ) 140 | -------------------------------------------------------------------------------- /tests/bad-version.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 0.1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: en 7 | language_alternate: fr 8 | charset: utf8 9 | parentidentifier: someparentid 10 | hierarchylevel: dataset 11 | datestamp: 2014-11-11 12 | dataseturi: http://some/minted/uri 13 | 14 | spatial: 15 | datatype: vector 16 | geomtype: point 17 | 18 | identification: 19 | language: eng; CAN 20 | charset: utf8 21 | title_en: title in English 22 | title_fr: title in French 23 | abstract_en: abstract in English 24 | abstract_fr: abstract in French 25 | dates: 26 | creation: 2011-11-11 27 | publication: 2000-09-01T00:00:00Z 28 | keywords: 29 | default: 30 | keywords_en: [kw1 in English,kw2 in English,kw3 in English] 31 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 32 | wmo: 33 | keywords_en: [FOO,BAR] 34 | keywords_type: theme 35 | gc_cst: 36 | keywords_en: [kw1,kw2] 37 | keywords_fr: [kw1,kw2] 38 | topiccategory: 39 | - climatologyMeteorologyAtmosphere 40 | extents: 41 | spatial: 42 | - bbox: [-141,42,-52,84] 43 | crs: 4326 44 | temporal: 45 | - begin: 1950-07-31 46 | end: now 47 | fees: None 48 | accessconstraints: otherRestrictions 49 | rights_en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 50 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 51 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 52 | status: onGoing 53 | maintenancefrequency: continual 54 | 55 | contact: 56 | pointOfContact: &contact_poc 57 | organization: Environment Canada 58 | url: http://www.ec.gc.ca/ 59 | individualname: Tom Kralidis 60 | positionname: Senior Systems Scientist 61 | phone: +01-123-456-7890 62 | fax: +01-123-456-7890 63 | address: 4905 Dufferin Street 64 | city: Toronto 65 | administrativearea: Ontario 66 | postalcode: M3H 5T4 67 | country: Canada 68 | email: foo@bar.tld 69 | hoursofservice: 0700h - 1500h EST 70 | contactinstructions: email 71 | 72 | distributor: *contact_poc 73 | 74 | distribution: 75 | waf: 76 | url: http://dd.meteo.gc.ca 77 | type: WWW:LINK 78 | name: my waf 79 | description_en: description in English 80 | description_fr: description in French 81 | function: download 82 | 83 | wms: 84 | url: http://dd.meteo.gc.ca 85 | type: OGC:WMS 86 | name_en: roads 87 | name_fr: routes 88 | description_en: description in English 89 | description_fr: description in French 90 | function: download 91 | -------------------------------------------------------------------------------- /tests/base-distribution.yml: -------------------------------------------------------------------------------- 1 | waf: 2 | type: WWW:LINK 3 | function: download 4 | 5 | wms: 6 | type: OGC:WMS 7 | function: download 8 | -------------------------------------------------------------------------------- /tests/base-metadata.yml: -------------------------------------------------------------------------------- 1 | identifier: s1234 2 | datestamp: 2011-11-11 3 | -------------------------------------------------------------------------------- /tests/broken-yaml.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: fr 7 | charset: utf8 8 | parentidentifier: someparentid 9 | hierarchylevel: dataset 10 | datestamp: 2014-11-11 11 | dataseturi: http://some/minted/uri 12 | 13 | spatial: 14 | datatype: vector 15 | geomtype: point 16 | 17 | identification: 18 | language: fra; CAN 19 | charset: utf8 20 | title_fr: title in French 21 | abstract_fr: abstract in French 22 | keywords: 23 | default: 24 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 25 | keywords_type: theme 26 | gc_cst: 27 | keywords_en: [kw1,kw2] 28 | topiccategory: 29 | - climatologyMeteorologyAtmosphere 30 | some-other-value: test 31 | extents: 32 | spatial: 33 | - bbox: [-141,42,-52,84] 34 | crs: 4326 35 | temporal: 36 | - begin: 1950-07-31 37 | end: now 38 | dates: 39 | publication: 2000-09-01T00:00:00Z 40 | fees: None 41 | accessconstraints: otherRestrictions 42 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 43 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 44 | status: onGoing 45 | maintenancefrequency: continual 46 | 47 | contact: 48 | pointOfContact: 49 | organization: Environment Canada 50 | url: http://www.ec.gc.ca/ 51 | individualname: Tom Kralidis 52 | positionname: Senior Systems Scientist 53 | phone: +01-123-456-7890 54 | fax: +01-123-456-7890 55 | address: 4905 Dufferin Street 56 | city: Toronto 57 | administrativearea: Ontario 58 | postalcode: M3H 5T4 59 | country: Canada 60 | email: foo@bar.tld 61 | hoursofservice: 0700h - 1500h EST 62 | contactinstructions: email 63 | 64 | distribution: 65 | #ref: contact:main 66 | organization: Environment Canada 67 | url: http://www.ec.gc.ca/ 68 | individualname: Tom Kralidis 69 | positionname: Senior Systems Scientist 70 | phone: +01-123-456-7890 71 | fax: +01-123-456-7890 72 | address: 4905 Dufferin Street 73 | city: Toronto 74 | administrativearea: Ontario 75 | postalcode: M3H 5T4 76 | country: Canada 77 | email: foo@bar.tld 78 | hoursofservice: 0700h - 1500h EST 79 | contactinstructions: email 80 | 81 | distribution: 82 | waf: 83 | url: http://dd.meteo.gc.ca 84 | type: WWW:LINK 85 | name: my waf 86 | description_fr: description in French 87 | function: download 88 | 89 | wms: 90 | url: http://dd.meteo.gc.ca 91 | type: OGC:WMS 92 | name_fr: routes 93 | description_fr: description in French 94 | function: download 95 | -------------------------------------------------------------------------------- /tests/child.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | base_mcf: base-metadata.yml 6 | identifier: s5678 7 | 8 | distribution: 9 | base_mcf: base-distribution.yml 10 | waf: 11 | url: http://example.org/waf 12 | 13 | wms: 14 | url: http://example.org/wms 15 | -------------------------------------------------------------------------------- /tests/contact.yml: -------------------------------------------------------------------------------- 1 | contact: 2 | pointOfContact: &contact_poc 3 | organization: Environment Canada 4 | url: http://www.ec.gc.ca/ 5 | individualname: Tom Kralidis 6 | positionname: Senior Systems Scientist 7 | phone: +01-123-456-7890 8 | fax: +01-123-456-7890 9 | address: 4905 Dufferin Street 10 | city: Toronto 11 | administrativearea: Ontario 12 | postalcode: M3H 5T4 13 | country: Canada 14 | email: foo@bar.tld 15 | hoursofservice: 0700h - 1500h EST 16 | contactinstructions: email 17 | 18 | distributor: *contact_poc 19 | -------------------------------------------------------------------------------- /tests/dates-pre-1900.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: en 7 | language_alternate: fr 8 | charset: utf8 9 | parentidentifier: someparentid 10 | hierarchylevel: dataset 11 | datestamp: 2014-11-11 12 | dataseturi: http://some/minted/uri 13 | 14 | spatial: 15 | datatype: vector 16 | geomtype: point 17 | 18 | identification: 19 | language: eng; CAN 20 | charset: utf8 21 | title_en: title in English 22 | title_fr: title in French 23 | abstract_en: abstract in English 24 | abstract_fr: abstract in French 25 | dates: 26 | creation: 1871-11-11 27 | publication: 2000-09-01T00:00:00Z 28 | keywords: 29 | default: 30 | keywords_en: [kw1 in English,kw2 in English,kw3 in English] 31 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 32 | wmo: 33 | keywords_en: [FOO,BAR] 34 | keywords_type: theme 35 | gc_cst: 36 | keywords_en: [kw1,kw2] 37 | keywords_fr: [kw1,kw2] 38 | topiccategory: 39 | - climatologyMeteorologyAtmosphere 40 | extents: 41 | spatial: 42 | - bbox: [-141,42,-52,84] 43 | crs: 4326 44 | temporal: 45 | - begin: 1950-07-31 46 | end: now 47 | fees: None 48 | accessconstraints: otherRestrictions 49 | rights_en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 50 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 51 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 52 | status: onGoing 53 | maintenancefrequency: continual 54 | 55 | contact: 56 | pointOfContact: &contact_poc 57 | organization: Environment Canada 58 | url: http://www.ec.gc.ca/ 59 | individualname: Tom Kralidis 60 | positionname: Senior Systems Scientist 61 | phone: +01-123-456-7890 62 | fax: +01-123-456-7890 63 | address: 4905 Dufferin Street 64 | city: Toronto 65 | administrativearea: Ontario 66 | postalcode: M3H 5T4 67 | country: Canada 68 | email: foo@bar.tld 69 | hoursofservice: 0700h - 1500h EST 70 | contactinstructions: email 71 | 72 | distributor: *contact_poc 73 | 74 | distribution: 75 | waf: 76 | url: http://dd.meteo.gc.ca 77 | type: WWW:LINK 78 | name: my waf 79 | description_en: description in English 80 | description_fr: description in French 81 | function: download 82 | 83 | wms: 84 | url: http://dd.meteo.gc.ca 85 | type: OGC:WMS 86 | name_en: roads 87 | name_fr: routes 88 | description_en: description in English 89 | description_fr: description in French 90 | function: download 91 | -------------------------------------------------------------------------------- /tests/deep-nest-child.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | base_mcf: deep-nest-parent.yml 5 | 6 | metadata: 7 | identifier: MYID 8 | language: en 9 | language_alternate: fr 10 | charset: utf8 11 | parentidentifier: someparentid 12 | hierarchylevel: dataset 13 | datestamp: 2014-11-11 14 | dataseturi: http://some/minted/uri 15 | 16 | spatial: 17 | datatype: vector 18 | geomtype: point 19 | 20 | identification: 21 | language: eng; CAN 22 | charset: utf8 23 | title_en: child title 24 | title_fr: title in French 25 | abstract_en: abstract in English 26 | abstract_fr: abstract in French 27 | dates: 28 | creation: 2011-11-11 29 | publication: 2000-09-01T00:00:00Z 30 | keywords: 31 | default: 32 | keywords_en: [kw1 in English,kw2 in English,kw3 in English] 33 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 34 | wmo: 35 | keywords_en: [FOO,BAR] 36 | keywords_type: theme 37 | gc_cst: 38 | keywords_en: [kw1,kw2] 39 | keywords_fr: [kw1,kw2] 40 | topiccategory: 41 | - climatologyMeteorologyAtmosphere 42 | extents: 43 | spatial: 44 | - bbox: [-141,42,-52,84] 45 | crs: 4326 46 | temporal: 47 | - begin: 1950-07-31 48 | end: now 49 | fees: None 50 | accessconstraints: otherRestrictions 51 | rights_en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 52 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 53 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 54 | status: onGoing 55 | maintenancefrequency: continual 56 | -------------------------------------------------------------------------------- /tests/deep-nest-parent.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | base_mcf: contact.yml 5 | 6 | metadata: 7 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 8 | language: en 9 | language_alternate: fr 10 | charset: utf8 11 | parentidentifier: someparentid 12 | hierarchylevel: dataset 13 | datestamp: 2014-11-11 14 | dataseturi: http://some/minted/uri 15 | 16 | spatial: 17 | datatype: vector 18 | geomtype: point 19 | 20 | identification: 21 | language: eng; CAN 22 | charset: utf8 23 | title_en: title in English 24 | title_fr: title in French 25 | abstract_en: abstract in English 26 | abstract_fr: abstract in French 27 | dates: 28 | creation: 2011-11-11 29 | publication: 2000-09-01T00:00:00Z 30 | keywords: 31 | default: 32 | keywords_en: [kw1 in English,kw2 in English,kw3 in English] 33 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 34 | wmo: 35 | keywords_en: [FOO,BAR] 36 | keywords_type: theme 37 | gc_cst: 38 | keywords_en: [kw1,kw2] 39 | keywords_fr: [kw1,kw2] 40 | topiccategory: 41 | - climatologyMeteorologyAtmosphere 42 | extents: 43 | spatial: 44 | - bbox: [-141,42,-52,84] 45 | crs: 4326 46 | temporal: 47 | - begin: 1950-07-31 48 | end: now 49 | fees: None 50 | accessconstraints: otherRestrictions 51 | rights_en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 52 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 53 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 54 | status: onGoing 55 | maintenancefrequency: continual 56 | 57 | distribution: 58 | waf: 59 | url: http://dd.meteo.gc.ca 60 | type: WWW:LINK 61 | name: my waf 62 | description_en: description in English 63 | description_fr: description in French 64 | function: download 65 | 66 | wms: 67 | url: http://dd.meteo.gc.ca 68 | type: OGC:WMS 69 | name_en: roads 70 | name_fr: routes 71 | description_en: description in English 72 | description_fr: description in French 73 | function: download 74 | -------------------------------------------------------------------------------- /tests/missing-version.yml: -------------------------------------------------------------------------------- 1 | metadata: 2 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 3 | language: en 4 | language_alternate: fr 5 | charset: utf8 6 | parentidentifier: someparentid 7 | hierarchylevel: dataset 8 | datestamp: 2014-11-11 9 | dataseturi: http://some/minted/uri 10 | 11 | spatial: 12 | datatype: vector 13 | geomtype: point 14 | 15 | identification: 16 | language: eng; CAN 17 | charset: utf8 18 | title_en: title in English 19 | title_fr: title in French 20 | abstract_en: abstract in English 21 | abstract_fr: abstract in French 22 | dates: 23 | creation: 2011-11-11 24 | publication: 2000-09-01T00:00:00Z 25 | keywords: 26 | default: 27 | keywords_en: [kw1 in English,kw2 in English,kw3 in English] 28 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 29 | wmo: 30 | keywords_en: [FOO,BAR] 31 | keywords_type: theme 32 | gc_cst: 33 | keywords_en: [kw1,kw2] 34 | keywords_fr: [kw1,kw2] 35 | topiccategory: 36 | - climatologyMeteorologyAtmosphere 37 | extents: 38 | spatial: 39 | - bbox: [-141,42,-52,84] 40 | crs: 4326 41 | temporal: 42 | - begin: 1950-07-31 43 | end: now 44 | fees: None 45 | accessconstraints: otherRestrictions 46 | rights_en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 47 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 48 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 49 | status: onGoing 50 | maintenancefrequency: continual 51 | 52 | contact: 53 | pointOfContacat: &contact_poc 54 | organization: Environment Canada 55 | url: http://www.ec.gc.ca/ 56 | individualname: Tom Kralidis 57 | positionname: Senior Systems Scientist 58 | phone: +01-123-456-7890 59 | fax: +01-123-456-7890 60 | address: 4905 Dufferin Street 61 | city: Toronto 62 | administrativearea: Ontario 63 | postalcode: M3H 5T4 64 | country: Canada 65 | email: foo@bar.tld 66 | hoursofservice: 0700h - 1500h EST 67 | contactinstructions: email 68 | 69 | distributor: *contact_poc 70 | 71 | distribution: 72 | waf: 73 | url: http://dd.meteo.gc.ca 74 | type: WWW:LINK 75 | name: my waf 76 | description_en: description in English 77 | description_fr: description in French 78 | function: download 79 | 80 | wms: 81 | url: http://dd.meteo.gc.ca 82 | type: OGC:WMS 83 | name_en: roads 84 | name_fr: routes 85 | description_en: description in English 86 | description_fr: description in French 87 | function: download 88 | -------------------------------------------------------------------------------- /tests/nil-identification-language.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: en 7 | language_alternate: fr 8 | charset: utf8 9 | parentidentifier: someparentid 10 | hierarchylevel: dataset 11 | datestamp: $date$ 12 | dataseturi: http://some/minted/uri 13 | 14 | spatial: 15 | datatype: vector 16 | geomtype: point 17 | 18 | identification: 19 | language: missing 20 | charset: utf8 21 | title_en: title in English 22 | title_fr: title in French 23 | abstract_en: abstract in English 24 | abstract_fr: abstract in French 25 | keywords: 26 | default: 27 | keywords_en: [kw1 in English,kw2 in English,kw3 in English] 28 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 29 | keywords_type: theme 30 | wmo: 31 | keywords_en: FOO,BAR 32 | keywords_type: theme 33 | gc_cst: 34 | keywords_en: [kw1,kw2] 35 | keywords_fr: [kw1,kw2] 36 | keywords_type: theme 37 | topiccategory: 38 | - climatologyMeteorologyAtmosphere 39 | extents: 40 | spatial: 41 | - bbox: [-141,42,-52,84] 42 | crs: 4326 43 | temporal: 44 | - begin: 1950-07-31 45 | end: now 46 | dates: 47 | publication: 2000-09-01T00:00:00Z 48 | fees: None 49 | accessconstraints: otherRestrictions 50 | rights_en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 51 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 52 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 53 | status: onGoing 54 | maintenancefrequency: continual 55 | 56 | contact: 57 | pointOfContact: 58 | organization: Environment Canada 59 | url: http://www.ec.gc.ca/ 60 | individualname: Tom Kralidis 61 | positionname: Senior Systems Scientist 62 | phone: +01-123-456-7890 63 | fax: +01-123-456-7890 64 | address: 4905 Dufferin Street 65 | city: Toronto 66 | administrativearea: Ontario 67 | postalcode: M3H 5T4 68 | country: Canada 69 | email: foo@bar.tld 70 | hoursofservice: 0700h - 1500h EST 71 | contactinstructions: email 72 | 73 | distributor: 74 | #ref: contact:main 75 | organization: Environment Canada 76 | url: http://www.ec.gc.ca/ 77 | individualname: Tom Kralidis 78 | positionname: Senior Systems Scientist 79 | phone: +01-123-456-7890 80 | fax: +01-123-456-7890 81 | address: 4905 Dufferin Street 82 | city: Toronto 83 | administrativearea: Ontario 84 | postalcode: M3H 5T4 85 | country: Canada 86 | email: foo@bar.tld 87 | hoursofservice: 0700h - 1500h EST 88 | contactinstructions: email 89 | 90 | distribution: 91 | waf: 92 | url: http://dd.meteo.gc.ca 93 | type: WWW:LINK 94 | name: my waf 95 | description_en: description in English 96 | description_fr: description in French 97 | function: download 98 | 99 | wms: 100 | url: http://dd.meteo.gc.ca 101 | type: OGC:WMS 102 | name_en: roads 103 | name_fr: routes 104 | description_en: description in English 105 | description_fr: description in French 106 | function: download 107 | -------------------------------------------------------------------------------- /tests/sample-child.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | base_mcf: base-metadata.yml 6 | language: en 7 | language_alternate: fr 8 | charset: utf8 9 | parentidentifier: someparentid 10 | hierarchylevel: dataset 11 | dataseturi: http://some/minted/uri 12 | 13 | spatial: 14 | datatype: vector 15 | geomtype: point 16 | 17 | identification: 18 | language: eng; CAN 19 | charset: utf8 20 | title: 21 | en: title in English 22 | fr: title in French 23 | abstract: 24 | en: abstract in English 25 | fr: abstract in French 26 | dates: 27 | creation: 2011-11-11 28 | publication: 2000-09-01T00:00:00Z 29 | keywords: 30 | default: 31 | keywords: 32 | en: [kw1 in English,kw2 in English,kw3 in English] 33 | fr: [kw1 in French,kw2 in French,kw3 in French] 34 | wmo: 35 | keywords: 36 | en: [FOO,BAR] 37 | keywords_type: theme 38 | vocabulary: 39 | name: 40 | en: My vocabulary 41 | fr: Mon vocabulaire 42 | url: http://example.org/vocab 43 | gc_cst: 44 | keywords: 45 | en: [kw1,kw2] 46 | fr: [kw1,kw2] 47 | topiccategory: 48 | - climatologyMeteorologyAtmosphere 49 | extents: 50 | spatial: 51 | - bbox: [-141,42,-52,84] 52 | crs: 4326 53 | temporal: 54 | - begin: 1950-07-31 55 | end: now 56 | fees: None 57 | accessconstraints: otherRestrictions 58 | rights: 59 | en: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 60 | fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 61 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 62 | status: onGoing 63 | maintenancefrequency: continual 64 | 65 | content_info: 66 | type: image 67 | cloud_cover: 72 68 | processing_level: "1.0" 69 | dimensions: 70 | - name: B1 71 | units: nm 72 | min: 932 73 | max: 958 74 | 75 | # platform metadata, applicable to iso19139-2 output 76 | acquisition: 77 | platforms: 78 | - identifier: LANDSAT_8 79 | description: Landsat 8 80 | instruments: 81 | - identifier: OLI_TIRS 82 | type: INS-NOBS 83 | 84 | contact: 85 | pointOfContact: &contact_poc 86 | organization: Environment Canada 87 | url: https://www.ec.gc.ca/ 88 | individualname: Tom Kralidis 89 | positionname: Senior Systems Scientist 90 | phone: +01-123-456-7890 91 | fax: +01-123-456-7890 92 | address: 4905 Dufferin Street 93 | city: Toronto 94 | administrativearea: Ontario 95 | postalcode: M3H 5T4 96 | country: Canada 97 | email: foo@bar.tld 98 | hoursofservice: 0700h - 1500h EST 99 | contactinstructions: email 100 | 101 | distributor: *contact_poc 102 | 103 | distribution: 104 | waf: 105 | url: https://dd.meteo.gc.ca 106 | type: WWW:LINK 107 | name: my waf 108 | description: 109 | en: description in English 110 | fr: description in French 111 | function: download 112 | 113 | wms: 114 | url: https://dd.meteo.gc.ca 115 | type: OGC:WMS 116 | name: 117 | en: roads 118 | fr: routes 119 | description: 120 | en: description in English 121 | fr: description in French 122 | function: download 123 | 124 | dataquality: 125 | scope: 126 | level: dataset 127 | lineage: 128 | statement: this dataset was derived from a custom process against dataset xyz 129 | -------------------------------------------------------------------------------- /tests/sample_schema/__init__.py: -------------------------------------------------------------------------------- 1 | # ================================================================= 2 | # 3 | # Terms and Conditions of Use 4 | # 5 | # Unless otherwise noted, computer program source code of this 6 | # distribution # is covered under Crown Copyright, Government of 7 | # Canada, and is distributed under the MIT License. 8 | # 9 | # The Canada wordmark and related graphics associated with this 10 | # distribution are protected under trademark law and copyright law. 11 | # No permission is granted to use them outside the parameters of 12 | # the Government of Canada's corporate identity program. For 13 | # more information, see 14 | # http://www.tbs-sct.gc.ca/fip-pcim/index-eng.asp 15 | # 16 | # Copyright title to all 3rd party software distributed with this 17 | # software is held by the respective copyright holders as noted in 18 | # those files. Users are asked to read the 3rd Party Licenses 19 | # referenced with those assets. 20 | # 21 | # Copyright (c) 2022 Tom Kralidis 22 | # 23 | # Permission is hereby granted, free of charge, to any person 24 | # obtaining a copy of this software and associated documentation 25 | # files (the "Software"), to deal in the Software without 26 | # restriction, including without limitation the rights to use, 27 | # copy, modify, merge, publish, distribute, sublicense, and/or sell 28 | # copies of the Software, and to permit persons to whom the 29 | # Software is furnished to do so, subject to the following 30 | # conditions: 31 | # 32 | # The above copyright notice and this permission notice shall be 33 | # included in all copies or substantial portions of the Software. 34 | # 35 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 36 | # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 37 | # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 38 | # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 39 | # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 40 | # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 41 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 42 | # OTHER DEALINGS IN THE SOFTWARE. 43 | # 44 | # ================================================================= 45 | 46 | import os 47 | 48 | from pygeometa.schemas.base import BaseOutputSchema 49 | 50 | THISDIR = os.path.dirname(os.path.realpath(__file__)) 51 | 52 | 53 | class SampleOutputSchema(BaseOutputSchema): 54 | """Sample output schema""" 55 | 56 | def __init__(self): 57 | """ 58 | Initialize object 59 | 60 | :returns: pygeometa.schemas.base.BaseOutputSchema 61 | """ 62 | 63 | super().__init__('sample', 'json', THISDIR) 64 | 65 | def write(self, mcf: dict) -> str: 66 | """ 67 | Write MCF into sample schema output 68 | """ 69 | 70 | return f"MCF_FILE_VERSION: {mcf['mcf']['version']}" 71 | -------------------------------------------------------------------------------- /tests/sample_schema_j2/contact.j2: -------------------------------------------------------------------------------- 1 | {{ record['contact']['pointOfContact']['individualname'] }} 2 | -------------------------------------------------------------------------------- /tests/sample_schema_j2/main.j2: -------------------------------------------------------------------------------- 1 | 2 | 3 | {{ record['metadata']['identifier'] }} 4 | {% include 'contact.j2' %} 5 | 6 | -------------------------------------------------------------------------------- /tests/unilingual.yml: -------------------------------------------------------------------------------- 1 | mcf: 2 | version: 1.0 3 | 4 | metadata: 5 | identifier: 3f342f64-9348-11df-ba6a-0014c2c00eab 6 | language: fr 7 | charset: utf8 8 | parentidentifier: someparentid 9 | hierarchylevel: dataset 10 | datestamp: 2014-11-11 11 | dataseturi: http://some/minted/uri 12 | 13 | spatial: 14 | datatype: vector 15 | geomtype: point 16 | 17 | identification: 18 | language: fra; CAN 19 | charset: utf8 20 | title_fr: title in French 21 | abstract_fr: abstract in French 22 | keywords: 23 | default: 24 | keywords_fr: [kw1 in French,kw2 in French,kw3 in French] 25 | keywords_type: theme 26 | gc_cst: 27 | keywords_en: [kw1,kw2] 28 | topiccategory: 29 | - climatologyMeteorologyAtmosphere 30 | dates: 31 | publication: 2000-09-01T00:00:00Z 32 | fees: None 33 | accessconstraints: otherRestrictions 34 | rights_fr: Copyright (c) 2010 Her Majesty the Queen in Right of Canada 35 | url: http://geogratis.ca/geogratis/en/product/search.do?id=08DB5E85-7405-FE3A-2860-CC3663245625 36 | extents: 37 | spatial: 38 | - bbox: [-141,42,-52,84] 39 | crs: 4326 40 | temporal: 41 | - begin: 1950-07-31 42 | end: now 43 | status: onGoing 44 | maintenancefrequency: continual 45 | 46 | contact: 47 | pointOfContact: 48 | organization: Environment Canada 49 | url: http://www.ec.gc.ca/ 50 | individualname: Tom Kralidis 51 | positionname: Senior Systems Scientist 52 | phone: +01-123-456-7890 53 | fax: +01-123-456-7890 54 | address: 4905 Dufferin Street 55 | city: Toronto 56 | administrativearea: Ontario 57 | postalcode: M3H 5T4 58 | country: Canada 59 | email: foo@bar.tld 60 | hoursofservice: 0700h - 1500h EST 61 | contactinstructions: email 62 | 63 | distributor: 64 | #ref: contact:main 65 | organization: Environment Canada 66 | url: http://www.ec.gc.ca/ 67 | individualname: Tom Kralidis 68 | positionname: Senior Systems Scientist 69 | phone: +01-123-456-7890 70 | fax: +01-123-456-7890 71 | address: 4905 Dufferin Street 72 | city: Toronto 73 | administrativearea: Ontario 74 | postalcode: M3H 5T4 75 | country: Canada 76 | email: foo@bar.tld 77 | hoursofservice: 0700h - 1500h EST 78 | contactinstructions: email 79 | 80 | distribution: 81 | waf: 82 | url: http://dd.meteo.gc.ca 83 | type: WWW:LINK 84 | name: my waf 85 | description_fr: description in French 86 | function: download 87 | 88 | wms: 89 | url: http://dd.meteo.gc.ca 90 | type: OGC:WMS 91 | name_fr: routes 92 | description_fr: description in French 93 | function: download 94 | --------------------------------------------------------------------------------