diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 460f754f2f5f0b26fcac344389a2e95adf46a713..bae2cc3a153892b66f9d50ea801b85be75c4ce75 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ variables: PGPASSWORD: postgres stages: + - init - test - deploy - pages @@ -17,7 +18,26 @@ cache: paths: - public/docs/ -#### Test #### + +### Static Badges ### +version: + stage: init + tags: + - linux + - opensuse + only: + - master + - tags + script: + - chmod +x ./CI/update_badge.sh + artifacts: + name: pages + when: always + paths: + - badges/ + + +#### Tests #### test: stage: test services: @@ -27,6 +47,9 @@ test: - public-docker variables: FAILURE_THRESHOLD: 85 + before_script: + - chmod +x ./CI/update_badge.sh + - ./CI/update_badge.sh > /dev/null script: - apt-get update && apt-get install -y postgresql-client - pip install --upgrade pip @@ -34,6 +57,43 @@ test: - psql -h postgres -U $POSTGRES_USER -d $POSTGRES_DB -f tests/fixtures/toardb_pytest.psql - chmod +x ./CI/do_pytest.sh - ./CI/do_pytest.sh + after_script: + - ./CI/update_badge.sh > /dev/null + artifacts: + name: pages + when: always + paths: + - badges/ + +coverage: + stage: test + services: + - name: postgis/postgis + alias: postgres + tags: + - public-docker + variables: + FAILURE_THRESHOLD: 50 + COVERAGE_PASS_THRESHOLD: 45 + before_script: + - chmod +x ./CI/update_badge.sh + - ./CI/update_badge.sh > /dev/null + script: + - apt-get update && apt-get install -y postgresql-client + - pip install --upgrade pip + - pip install coverage + - pip install --no-cache-dir -r requirements.txt + - psql -h postgres -U $POSTGRES_USER -d $POSTGRES_DB -f tests/fixtures/toardb_pytest.psql + - chmod +x ./CI/do_pytest_coverage.sh + - ./CI/do_pytest_coverage.sh + after_script: + - ./CI/update_badge.sh > /dev/null + artifacts: + name: pages + when: always + paths: + - badges/ + - coverage/ #### Documentation #### docs: @@ -68,6 +128,12 @@ pages: - dev when: always script: + - mkdir -p public/badges/ + - cp -af badges/badge_*.svg public/badges/ + - ls public/badges/ + - mkdir -p public/coverage + - cp -af coverage/. public/coverage + - ls public/coverage - mkdir -p public/docs - cp -af docs/_build/html/* public/docs - ls public/docs/ @@ -76,4 +142,10 @@ pages: when: always paths: - public/ - + - badges/ + - coverage/ + cache: + key: old-pages + paths: + - public/badges/ + - public/coverage/ diff --git a/CI/do_pytest_coverage.sh b/CI/do_pytest_coverage.sh new file mode 100644 index 0000000000000000000000000000000000000000..537abadcc7b3a81b1c5a4c093362b217ebc6f7b4 --- /dev/null +++ b/CI/do_pytest_coverage.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash +export PYTHONPATH=/builds/esde/toar-data/toardb_fastapi/ + +# run coverage twice, 1) for html deploy 2) for success evaluation +coverage run -m pytest tests +coverage html +coverage report | tee coverage_results.out + +IS_FAILED=$? + +# move html coverage report +mkdir coverage/ +BRANCH_NAME=$( echo -e "${CI_COMMIT_REF_NAME////_}") +mkdir coverage/${BRANCH_NAME} +mkdir coverage/recent +cp -r htmlcov/* coverage/${BRANCH_NAME}/. +cp -r htmlcov/* coverage/recent/. +if [[ "${CI_COMMIT_REF_NAME}" = "master" ]]; then + cp -r htmlcov/* coverage/. +fi + +# extract coverage information +COVERAGE_RATIO="$(grep -oP '\d+\%' coverage_results.out | tail -1)" +COVERAGE_RATIO="$(echo ${COVERAGE_RATIO} | (grep -oP '\d*'))" + +# report +if [[ ${IS_FAILED} == 0 ]]; then + if [[ ${COVERAGE_RATIO} -lt ${COVERAGE_PASS_THRESHOLD} ]]; then + echo "only ${COVERAGE_RATIO}% covered" + echo "incomplete" > status.txt + echo "${COVERAGE_RATIO}%25" > incomplete.txt + if [[ ${COVERAGE_RATIO} -lt ${FAILURE_THRESHOLD} ]]; then + echo -e "\033[1;31monly ${COVERAGE_RATIO}% covered!!\033[0m" + exit 1 + fi + else + echo "passed" + echo "success" > status.txt + echo "${COVERAGE_RATIO}%25" > success.txt + fi + exit 0 +else + echo "not passed" + exit 1 +fi diff --git a/CI/update_badge.sh b/CI/update_badge.sh new file mode 100644 index 0000000000000000000000000000000000000000..c8b11015d27f509faeb4b26b5d88ec7df5a4e675 --- /dev/null +++ b/CI/update_badge.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# 'running', 'success' or 'failure' is in this file +if [[ -e status.txt ]]; then + EXIT_STATUS=`cat status.txt` +else + EXIT_STATUS="running" +fi + +printf "%s\n" ${EXIT_STATUS} + +# fetch badge_status +BADGE_STATUS="${CI_COMMIT_REF_NAME}:${CI_JOB_NAME}" +# replace - with -- +BADGE_STATUS=$( echo -e "${BADGE_STATUS//\-/--}") + + +# Set values for shields.io fields based on STATUS +if [[ ${EXIT_STATUS} = "running" ]]; then + BADGE_SUBJECT="running" + BADGE_COLOR="lightgrey" +elif [[ ${EXIT_STATUS} = "failure" ]]; then + BADGE_SUBJECT="failed" + BADGE_COLOR="red" +elif [[ ${EXIT_STATUS} = "success" ]]; then + BADGE_SUBJECT="passed" + BADGE_COLOR="brightgreen" + if [[ -e success.txt ]]; then + SUCCESS_MESSAGE=`cat success.txt` + BADGE_SUBJECT="${SUCCESS_MESSAGE}" + fi +elif [[ ${EXIT_STATUS} = "incomplete" ]]; then + EXIT_STATUS_MESSAGE=`cat incomplete.txt` + BADGE_SUBJECT="${EXIT_STATUS_MESSAGE}" + EXIT_STATUS_RATIO="$(echo ${EXIT_STATUS_MESSAGE} | (grep -oP '\d*') | head -1)" + printf "%s\n" ${EXIT_STATUS_RATIO} + if [[ "${EXIT_STATUS_RATIO}" -lt "${FAILURE_THRESHOLD}" ]]; then + BADGE_COLOR="red" + else + BADGE_COLOR="yellow" + fi +else + exit 1 +fi + +# load additional options +while getopts b:c:s: option +do + case ${option} in + b) BADGE_STATUS=$( echo -e "${OPTARG//\-/--}");; + c) BADGE_COLOR=$( echo -e "${OPTARG//\-/--}");; + s) BADGE_SUBJECT=$( echo -e "${OPTARG//\-/--}");; + esac +done + + +# Set filename for the badge (i.e. 'ci-test-branch-job.svg') +CI_COMMIT_REF_NAME_NO_SLASH="$( echo -e "${CI_COMMIT_REF_NAME}" | tr '/' '_' )" +if [[ ${BADGE_STATUS} = "version" ]]; then + BADGE_FILENAME="badge_version.svg" +else + BADGE_FILENAME="badge_${CI_COMMIT_REF_NAME_NO_SLASH}-${CI_JOB_NAME}.svg" +fi +RECENT_BADGE_FILENAME="badge_recent-${CI_JOB_NAME}.svg" + +# Get the badge from shields.io +SHIELDS_IO_NAME=${BADGE_STATUS}-${BADGE_SUBJECT}-${BADGE_COLOR}.svg +printf "%s\n" "INFO: Fetching badge ${SHIELDS_IO_NAME} from shields.io to ${BADGE_FILENAME}." +printf "%s\n" "${SHIELDS_IO_NAME//\_/__}" +printf "%s\n" "${SHIELDS_IO_NAME//\#/%23}" + +SHIELDS_IO_NAME="$( echo -e "${SHIELDS_IO_NAME//\_/__}" )" +SHIELDS_IO_NAME="$( echo -e "${SHIELDS_IO_NAME//\#/%23}")" +curl "https://img.shields.io/badge/${SHIELDS_IO_NAME}" > ${BADGE_FILENAME} +echo "https://img.shields.io/badge/${SHIELDS_IO_NAME}" +SHIELDS_IO_NAME_RECENT="RECENT:${SHIELDS_IO_NAME}" +curl "https://img.shields.io/badge/${SHIELDS_IO_NAME_RECENT}" > ${RECENT_BADGE_FILENAME} +echo "${SHIELDS_IO_NAME_RECENT}" > testRecentName.txt + +# +if [[ ! -d ./badges ]]; then + # Control will enter here if $DIRECTORY doesn't exist. + mkdir badges/ +fi +mv ${BADGE_FILENAME} ./badges/. + +# replace outdated recent badge by new badge +mv ${RECENT_BADGE_FILENAME} ./badges/${RECENT_BADGE_FILENAME} + +# set status to failed, this will be overwritten if job ended with exitcode 0 +echo "failed" > status.txt + +exit 0 diff --git a/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.6--0.7.7.sql b/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.6--0.7.7.sql new file mode 100644 index 0000000000000000000000000000000000000000..5faa84163c64ad012ed871e17fdef35a99cffaef --- /dev/null +++ b/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.6--0.7.7.sql @@ -0,0 +1,33 @@ +-- +-- toardb/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.6--0.7.7.sql +-- +-- [Step to install] +-- +-- 1. +-- + +-- INSTALL VERSION: '0.7.7' + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE/ALTER EXTENSION toar_controlled_vocabulary" to load this file. \quit + +-- still to do: +-- How to set convoc_schema to result from SELECT: +-- SELECT table_schema INTO convoc_schema FROM information_schema.tables WHERE table_name='df_vocabulary'; + +SET SCHEMA 'toar_convoc'; + +-- Stationmeta +-- =========== + +-- climatic zones +-- see: IPCC Climate Zone Map reported in Figure 3A.5.1 in Chapter 3 of Vol.4 of 2019 Refinement + +UPDATE CZ_vocabulary SET enum_display_str='-1 (undefined)' WHERE enum_val=-1; +UPDATE CZ_vocabulary SET enum_display_str='0 (unclassified)' WHERE enum_val=0; + +-- Station HTAP Regions (TIER1) +-- The integer denoting the “tier1” region defined in the task force on hemispheric transport of air pollution (TFHTAP) coordinated model studies. + +UPDATE TR_vocabulary SET (enum_val, enum_str, enum_display_str) = ( 5, 'HTAPTier1SAS', '5 (SAS South Asia: India, Nepal, Pakistan, Afghanistan, Bangladesh, Sri Lanka)') WHERE enum_val=5; + diff --git a/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.7.sql b/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.7.sql new file mode 100644 index 0000000000000000000000000000000000000000..0acde35d8ef3abdd1b24eab83d78a1314457b3fa --- /dev/null +++ b/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.7.sql @@ -0,0 +1,2224 @@ +-- +-- toardb/extension/toar_controlled_vocabulary/toar_controlled_vocabulary--0.7.7.sql +-- +-- [Step to install] +-- +-- 1. +-- + +-- INSTALL VERSION: '0.7.7' + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION toar_controlled_vocabulary" to load this file. \quit + +-- Roles +-- ===== + +-- Role Codes + +CREATE TABLE IF NOT EXISTS RC_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT rc_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO RC_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'PointOfContact', 'point of contact'), + (1, 'PrincipalInvestigator', 'principal investigator'), + (2, 'Originator', 'originator'), + (3, 'Contributor', 'contributor'), + (4, 'Collaborator', 'collaborator'), + (5, 'ResourceProvider', 'resource provider'), + (6, 'Custodian', 'custodian'), + (7, 'Stakeholder', 'stakeholder'), + (8, 'RightsHolder', 'rights holder'); + +-- Role Status + +CREATE TABLE IF NOT EXISTS RS_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT rs_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO RS_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Active', 'active'), + (1, 'Inactive', 'inactive'), + (2, 'Unknown', 'unknown'); + +-- Annotation +-- ========== + +-- Kind of Annotation + +CREATE TABLE IF NOT EXISTS AK_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT ak_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO AK_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'User', 'user comment'), + (1, 'Provider', 'provider comment'), + (2, 'Curator', 'curator comment'); + +-- Contacts +-- ======== + +-- Kind of Organizations + +CREATE TABLE IF NOT EXISTS OK_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT ok_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO OK_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (1, 'Government', 'government'), + (2, 'Research', 'research'), + (3, 'University', 'university'), + (4, 'International', 'international'), + (5, 'NonProfit', 'non-profit'), + (6, 'Commercial', 'commercial'), + (7, 'Individual', 'individual'), + (8, 'Other', 'other'); + +-- Changelogs +-- ========== + +-- Type of Change + +CREATE TABLE IF NOT EXISTS CL_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT cl_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO CL_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Created', 'created'), + (1, 'SingleValue', 'single value correction in metadata'), + (2, 'Comprehensive', 'comprehensive metadata revision'), + (3, 'Typo', 'typographic correction of metadata'), + (4, 'UnspecifiedData', 'unspecified data value corrections'), + (5, 'Replaced', 'replaced data with a new version'), + (6, 'Flagging', 'data value flagging'); + + +-- Timeseries +-- ========== + +-- Sampling Frequencies + +CREATE TABLE IF NOT EXISTS SF_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT sf_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO SF_vocabulary (enum_val, enum_str, enum_display_str) VALUES + ( 0, 'Hourly', 'hourly'), + ( 1, 'TenMinutes', '10-minutes'), + ( 2, 'FifteenMinutes', '15-minutes'), + ( 3, 'TwentyMinutes', '20-minutes'), + ( 4, 'ThirtyMinutes', '30-minutes'), + ( 5, 'ThreeHourly', '3-hourly'), + ( 6, 'SixHourly', '6-hourly'), + ( 7, 'Daily', 'daily'), + ( 8, 'Weekly', 'weekly'), + ( 9, 'Monthly', 'monthly'), + (10, 'Yearly', 'yearly'), + (11, 'Irregular', 'irregular data samples of constant length'), + (12, 'Irregular2', 'irregular data samples of varying length'); + +-- Aggregation Types + +CREATE TABLE IF NOT EXISTS AT_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT at_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO AT_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Mean', 'mean'), + (1, 'MeanOf2', 'mean of two values'), + (2, 'MeanOfWeek', 'weekly mean'), + (3, 'MeanOf4Samples', 'mean out of 4 samples'), + (4, 'MeanOfMonth', 'monthly mean'), + (5, 'None', 'none'), + (6, 'Unknown', 'unknown'); + +-- Data Origin Type + +CREATE TABLE IF NOT EXISTS OT_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT ot_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO OT_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Measurement', 'measurement'), + (1, 'Model', 'model'); + +-- Data Origin + +CREATE TABLE IF NOT EXISTS DO_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT do_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO DO_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Instrument', 'instrument'), + (1, 'COSMOREA6', 'COSMO REA 6'), + (2, 'ERA5', 'ERA5'); + +-- Stationmeta +-- =========== + +-- climatic zones +-- see: IPCC Climate Zone Map reported in Figure 3A.5.1 in Chapter 3 of Vol.4 of 2019 Refinement + +CREATE TABLE IF NOT EXISTS CZ_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT cz_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO CZ_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (-1, 'Undefined', '-1 (undefined)'), + ( 0, 'Unclassified', '0 (unclassified)'), + ( 1, 'TropicalMontane', '1 (tropical montane)'), + ( 2, 'TropicalWet', '2 (tropical wet)'), + ( 3, 'TropicalMoist', '3 (tropical moist)'), + ( 4, 'TropicalDry', '4 (tropical dry)'), + ( 5, 'WarmTemperateMoist', '5 (warm temperate moist)'), + ( 6, 'WarmTemperateDry', '6 (warm temperate dry)'), + ( 7, 'CoolTemperateMoist', '7 (cool temperate moist)'), + ( 8, 'CoolTemperateDry', '8 (cool temperate dry)'), + ( 9, 'BorealMoist', '9 (boreal moist)'), + (10, 'BorealDry', '10 (boreal dry)'), + (11, 'PolarMoist', '11 (polar moist)'), + (12, 'PolarDry', '12 (polar dry)'); + +-- Station Coordinate Validity + +CREATE TABLE IF NOT EXISTS CV_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT cv_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO CV_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'NotChecked', 'not checked'), + (1, 'Verified', 'verified'), + (2, 'Plausible', 'plausible'), + (3, 'Doubtful', 'doubtful'), + (4, 'Unverifyable', 'not verifyable'); + +-- Station Types + +CREATE TABLE IF NOT EXISTS ST_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT st_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO ST_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Unknown', 'unknown'), + (1, 'Background', 'background'), + (2, 'Traffic', 'traffic'), + (3, 'Industrial', 'industrial'); + +-- Station Types Of Area + +CREATE TABLE IF NOT EXISTS TA_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT ta_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO TA_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Unknown', 'unknown'), + (1, 'Urban', 'urban'), + (2, 'Suburban', 'suburban'), + (3, 'Rural', 'rural'); + +-- Station TOAR Categories + +CREATE TABLE IF NOT EXISTS TC_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT tc_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO TC_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (-1, 'Unknown', 'unknown'), + ( 0, 'Unclassified', 'unclassified'), + ( 1, 'RuralLowElevation', 'rural low elevation'), + ( 2, 'RuralHighElevation', 'rural high elevation'), + ( 3, 'Urban', 'urban'); + +-- Station HTAP Regions (TIER1) +-- The integer denoting the “tier1” region defined in the task force on hemispheric transport of air pollution (TFHTAP) coordinated model studies. + +CREATE TABLE IF NOT EXISTS TR_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT tr_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO TR_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (-1, 'HTAPTier1Undefined', '-1 (undefined)'), + ( 1, 'HTAPTier1World', '1 (World)'), + ( 2, 'HTAPTier1OCN', '2 (OCN Non-arctic/Antarctic Ocean)'), + ( 3, 'HTAPTier1NAM', '3 (NAM US+Canada (upto 66 N; polar circle))'), + ( 4, 'HTAPTier1EUR', '4 (EUR Western + Eastern EU+Turkey (upto 66 N polar circle))'), + ( 5, 'HTAPTier1SAS', '5 (SAS South Asia: India, Nepal, Pakistan, Afghanistan, Bangladesh, Sri Lanka)'), + ( 6, 'HTAPTier1EAS', '6 (EAS East Asia: China, Korea, Japan)'), + ( 7, 'HTAPTier1SEA', '7 (SEA South East Asia)'), + ( 8, 'HTAPTier1PAN', '8 (PAN Pacific, Australia+ New Zealand)'), + ( 9, 'HTAPTier1NAF', '9 (NAF Northern Africa+Sahara+Sahel)'), + (10, 'HTAPTier1SAF', '10 (SAF Sub Saharan/sub Sahel Africa)'), + (11, 'HTAPTier1MDE', '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)'), + (12, 'HTAPTier1MCA', '12 (MCA Mexico, Central America, Caribbean, Guyanas, Venezuela, Columbia)'), + (13, 'HTAPTier1SAM', '13 (SAM S. America)'), + (14, 'HTAPTier1RBU', '14 (RBU Russia, Belarussia, Ukraine)'), + (15, 'HTAPTier1CAS', '15 (CAS Central Asia)'), + (16, 'HTAPTier1NPO', '16 (NPO Arctic Circle (North of 66 N) + Greenland)'), + (17, 'HTAPTier1SPO', '17 (SPO Antarctic)'); + +-- Station Landcover Types +-- see: http://maps.elie.ucl.ac.be/CCI/viewer/download/ESACCI-LC-Ph2-PUGv2_2.0.pdf + +CREATE TABLE IF NOT EXISTS LC_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT dl_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO LC_vocabulary (enum_val, enum_str, enum_display_str) VALUES + ( -1, 'Undefined', '-1 (undefined)'), + ( 0, 'NoData', '0 (No Data)'), + ( 10, 'CroplandRainfed', '10 (Cropland, rainfed)'), + ( 11, 'CroplandRainfedHerbaceousCover', '11 (Cropland, rainfed, herbaceous cover)'), + ( 12, 'CroplandRainfedTreeOrShrubCover', '12 (Cropland, rainfed, tree or shrub cover)'), + ( 20, 'CroplandIrrigated', '20 (Cropland, irrigated or post-flooding)'), + ( 30, 'MosaicCropland', '30 (Mosaic cropland (>50%) / natural vegetation (tree, shrub, herbaceous cover) (<50%))'), + ( 40, 'MosaicNaturalVegetation', '40 (Mosaic natural vegetation (tree, shrub, herbaceous cover) (>50%) / cropland (<50%))'), + ( 50, 'TreeBroadleavedEvergreenClosedToOpen', '50 (Tree cover, broadleaved, evergreen, closed to open (>15%))'), + ( 60, 'TreeBroadleavedDeciduousClosedToOpen', '60 (Tree cover, broadleaved, deciduous, closed to open (>15%))'), + ( 61, 'TreeBroadleavedDeciduousClosed', '61 (Tree cover, broadleaved, deciduous, closed (>40%))'), + ( 62, 'TreeBroadleavedDeciduousOpen', '62 (Tree cover, broadleaved, deciduous, open (15-40%))'), + ( 70, 'TreeNeedleleavedEvergreenClosedToOpen', '70 (Tree cover, needleleaved, evergreen, closed to open (>15%))'), + ( 71, 'TreeNeedleleavedEvergreenClosed', '71 (Tree cover, needleleaved, evergreen, closed (>40%))'), + ( 72, 'TreeNeedleleavedEvergreenOpen', '72 (Tree cover, needleleaved, evergreen, open (15-40%))'), + ( 80, 'TreeNeedleleavedDeciduousClosedToOpen', '80 (Tree cover, needleleaved, deciduous, closed to open (>15%))'), + ( 81, 'TreeNedleleavedDeciduousClosed', '81 (Tree cover, needleleaved, deciduous, closed (>40%))'), + ( 82, 'TreeNeedleleavedDeciduousOpen', '82 (Tree cover, needleleaved, deciduous, open (15-40%))'), + ( 90, 'TreeMixed', '90 (Tree cover, mixed leaf type (broadleaved and needleleaved))'), + (100, 'MosaicTreeAndShrub', '100 (Mosaic tree and shrub (>50%) / herbaceous cover (<50%))'), + (110, 'MosaicHerbaceous', '110 (Mosaic herbaceous cover (>50%) / tree and shrub (<50%))'), + (120, 'Shrubland', '120 (Shrubland)'), + (121, 'ShrublandEvergreen', '121 (Evergreen shrubland)'), + (122, 'ShrublandDeciduous', '122 (Deciduous shrubland)'), + (130, 'Grassland', '130 (Grassland)'), + (140, 'LichensAndMosses', '140 (Lichens and mosses)'), + (150, 'SparseVegetation', '150 (Sparse vegetation (tree, shrub, herbaceous cover) (<15%))'), + (151, 'SparseTree', '151 (Sparse tree (<15%))'), + (152, 'SparseShrub', '152 (Sparse shrub (<15%))'), + (153, 'SparseHerbaceous', '153 (Sparse herbaceous cover (<15%))'), + (160, 'TreeCoverFloodedFreshOrBrakishWater', '160 (Tree cover, flooded, fresh or brakish water)'), + (170, 'TreeCoverFloodedSalineWater', '170 (Tree cover, flooded, saline water)'), + (180, 'ShrubOrHerbaceousCoverFlooded', '180 (Shrub or herbaceous cover, flooded, fresh/saline/brakish water)'), + (190, 'Urban', '190 (Urban areas)'), + (200, 'BareAreas', '200 (Bare areas)'), + (201, 'BareAreasConsolidated', '201 (Consolidated bare areas)'), + (202, 'BareAreasUnconsolidated', '202 (Unconsolidated bare areas)'), + (210, 'Water', '210 (Water bodies)'), + (220, 'SnowAndIce', '220 (Permanent snow and ice)'); + +-- Station Eco Regions +-- see: https://ecoregions2017.appspot.com/ + +CREATE TABLE IF NOT EXISTS ER_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT er_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO ER_vocabulary (enum_val, enum_str, enum_display_str) VALUES + ( -1, 'Undefined', '-1 (undefined)'), + ( 0, 'RockandIce', '0 (Rock and Ice)'), + ( 1, 'AlbertineRiftmontaneforests', '1 (Albertine Rift montane forests)'), + ( 2, 'CameroonHighlandsforests', '2 (Cameroon Highlands forests)'), + ( 3, 'CentralCongolianlowlandforests', '3 (Central Congolian lowland forests)'), + ( 4, 'Comorosforests', '4 (Comoros forests)'), + ( 5, 'Congoliancoastalforests', '5 (Congolian coastal forests)'), + ( 6, 'CrossNigertransitionforests', '6 (Cross-Niger transition forests)'), + ( 7, 'CrossSanagaBiokocoastalforests', '7 (Cross-Sanaga-Bioko coastal forests)'), + ( 8, 'EastAfricanmontaneforests', '8 (East African montane forests)'), + ( 9, 'EasternArcforests', '9 (Eastern Arc forests)'), + ( 10, 'EasternCongolianswampforests', '10 (Eastern Congolian swamp forests)'), + ( 11, 'EasternGuineanforests', '11 (Eastern Guinean forests)'), + ( 12, 'Ethiopianmontaneforests', '12 (Ethiopian montane forests)'), + ( 13, 'GraniticSeychellesforests', '13 (Granitic Seychelles forests)'), + ( 14, 'Guineanmontaneforests', '14 (Guinean montane forests)'), + ( 15, 'KnysnaAmatolemontaneforests', '15 (Knysna-Amatole montane forests)'), + ( 16, 'KwazuluNatalCapecoastalforests', '16 (Kwazulu Natal-Cape coastal forests)'), + ( 17, 'Madagascarhumidforests', '17 (Madagascar humid forests)'), + ( 18, 'Madagascarsubhumidforests', '18 (Madagascar subhumid forests)'), + ( 19, 'Maputalandcoastalforestsandwoodlands', '19 (Maputaland coastal forests and woodlands)'), + ( 20, 'Mascareneforests', '20 (Mascarene forests)'), + ( 21, 'MountCameroonandBiokomontaneforests', '21 (Mount Cameroon and Bioko montane forests)'), + ( 22, 'NigerDeltaswampforests', '22 (Niger Delta swamp forests)'), + ( 23, 'Nigerianlowlandforests', '23 (Nigerian lowland forests)'), + ( 24, 'NortheastCongolianlowlandforests', '24 (Northeast Congolian lowland forests)'), + ( 25, 'NorthernSwahilicoastalforests', '25 (Northern Swahili coastal forests)'), + ( 26, 'NorthwestCongolianlowlandforests', '26 (Northwest Congolian lowland forests)'), + ( 27, 'SaoTomePrincipeandAnnobonforests', '27 (São Tomé, Príncipe, and Annobón forests)'), + ( 28, 'SouthernSwahilicoastalforestsandwoodlands', '28 (Southern Swahili coastal forests and woodlands)'), + ( 29, 'WesternCongolianswampforests', '29 (Western Congolian swamp forests)'), + ( 30, 'WesternGuineanlowlandforests', '30 (Western Guinean lowland forests)'), + ( 31, 'CapeVerdeIslandsdryforests', '31 (Cape Verde Islands dry forests)'), + ( 32, 'Madagascardrydeciduousforests', '32 (Madagascar dry deciduous forests)'), + ( 33, 'Zambezianevergreendryforests', '33 (Zambezian evergreen dry forests)'), + ( 34, 'Angolanmopanewoodlands', '34 (Angolan mopane woodlands)'), + ( 35, 'Angolanscarpsavannaandwoodlands', '35 (Angolan scarp savanna and woodlands)'), + ( 36, 'Angolanwetmiombowoodlands', '36 (Angolan wet miombo woodlands)'), + ( 37, 'Ascensionscrubandgrasslands', '37 (Ascension scrub and grasslands)'), + ( 38, 'Centralbushveld', '38 (Central bushveld)'), + ( 39, 'CentralZambezianwetmiombowoodlands', '39 (Central Zambezian wet miombo woodlands)'), + ( 40, 'DrakensbergEscarpmentsavannaandthicket', '40 (Drakensberg Escarpment savanna and thicket)'), + ( 41, 'Drakensberggrasslands', '41 (Drakensberg grasslands)'), + ( 42, 'Drymiombowoodlands', '42 (Dry miombo woodlands)'), + ( 43, 'EastSudaniansavanna', '43 (East Sudanian savanna)'), + ( 44, 'Guineanforestsavanna', '44 (Guinean forest-savanna)'), + ( 45, 'HornofAfricaxericbushlands', '45 (Horn of Africa xeric bushlands)'), + ( 46, 'ItigiSumbuthicket', '46 (Itigi-Sumbu thicket)'), + ( 47, 'KalahariAcaciawoodlands', '47 (Kalahari Acacia woodlands)'), + ( 48, 'Limpopolowveld', '48 (Limpopo lowveld)'), + ( 49, 'MandaraPlateauwoodlands', '49 (Mandara Plateau woodlands)'), + ( 50, 'Masaixericgrasslandsandshrublands', '50 (Masai xeric grasslands and shrublands)'), + ( 51, 'NorthernAcaciaCommiphorabushlandsandthickets', '51 (Northern Acacia-Commiphora bushlands and thickets)'), + ( 52, 'NorthernCongolianForestSavanna', '52 (Northern Congolian Forest-Savanna)'), + ( 53, 'SahelianAcaciasavanna', '53 (Sahelian Acacia savanna)'), + ( 54, 'Serengetivolcanicgrasslands', '54 (Serengeti volcanic grasslands)'), + ( 55, 'SomaliAcaciaCommiphorabushlandsandthickets', '55 (Somali Acacia-Commiphora bushlands and thickets)'), + ( 56, 'SouthArabianfogwoodlandsshrublandsanddune', '56 (South Arabian fog woodlands, shrublands, and dune)'), + ( 57, 'SouthernAcaciaCommiphorabushlandsandthickets', '57 (Southern Acacia-Commiphora bushlands and thickets)'), + ( 58, 'SouthernCongolianforestsavanna', '58 (Southern Congolian forest-savanna)'), + ( 59, 'SouthwestArabianmontanewoodlandsandgrasslands', '59 (Southwest Arabian montane woodlands and grasslands)'), + ( 60, 'StHelenascrubandwoodlands', '60 (St. Helena scrub and woodlands)'), + ( 61, 'VictoriaBasinforestsavanna', '61 (Victoria Basin forest-savanna)'), + ( 62, 'WestSudaniansavanna', '62 (West Sudanian savanna)'), + ( 63, 'WesternCongolianforestsavanna', '63 (Western Congolian forest-savanna)'), + ( 64, 'ZambezianBaikiaeawoodlands', '64 (Zambezian Baikiaea woodlands)'), + ( 65, 'Zambezianmopanewoodlands', '65 (Zambezian mopane woodlands)'), + ( 66, 'ZambezianLimpopomixedwoodlands', '66 (Zambezian-Limpopo mixed woodlands)'), + ( 67, 'AmsterdamSaintPaulIslandstemperategrasslands', '67 (Amsterdam-Saint Paul Islands temperate grasslands)'), + ( 68, 'TristanDaCunhaGoughIslandsshrubandgrasslands', '68 (Tristan Da Cunha-Gough Islands shrub and grasslands)'), + ( 69, 'EastAfricanhalophytics', '69 (East African halophytics)'), + ( 70, 'EtoshaPanhalophytics', '70 (Etosha Pan halophytics)'), + ( 71, 'InnerNigerDeltafloodedsavanna', '71 (Inner Niger Delta flooded savanna)'), + ( 72, 'LakeChadfloodedsavanna', '72 (Lake Chad flooded savanna)'), + ( 73, 'Makgadikgadihalophytics', '73 (Makgadikgadi halophytics)'), + ( 74, 'Suddfloodedgrasslands', '74 (Sudd flooded grasslands)'), + ( 75, 'Zambeziancoastalfloodedsavanna', '75 (Zambezian coastal flooded savanna)'), + ( 76, 'Zambezianfloodedgrasslands', '76 (Zambezian flooded grasslands)'), + ( 77, 'Angolanmontaneforestgrassland', '77 (Angolan montane forest-grassland)'), + ( 78, 'EastAfricanmontanemoorlands', '78 (East African montane moorlands)'), + ( 79, 'Ethiopianmontanegrasslandsandwoodlands', '79 (Ethiopian montane grasslands and woodlands)'), + ( 80, 'Ethiopianmontanemoorlands', '80 (Ethiopian montane moorlands)'), + ( 81, 'Highveldgrasslands', '81 (Highveld grasslands)'), + ( 82, 'JosPlateauforestgrassland', '82 (Jos Plateau forest-grassland)'), + ( 83, 'Madagascarericoidthickets', '83 (Madagascar ericoid thickets)'), + ( 84, 'MulanjeMontaneforestgrassland', '84 (Mulanje Montane forest-grassland)'), + ( 85, 'NyangaChimanimaniMontaneforestgrassland', '85 (Nyanga-Chimanimani Montane forest-grassland)'), + ( 86, 'RwenzoriVirungamontanemoorlands', '86 (Rwenzori-Virunga montane moorlands)'), + ( 87, 'SouthernRiftMontaneforestgrassland', '87 (Southern Rift Montane forest-grassland)'), + ( 88, 'Albanythickets', '88 (Albany thickets)'), + ( 89, 'Fynbosshrubland', '89 (Fynbos shrubland)'), + ( 90, 'Renosterveldshrubland', '90 (Renosterveld shrubland)'), + ( 91, 'AldabraIslandxericscrub', '91 (Aldabra Island xeric scrub)'), + ( 92, 'Djiboutixericshrublands', '92 (Djibouti xeric shrublands)'), + ( 93, 'Eritreancoastaldesert', '93 (Eritrean coastal desert)'), + ( 94, 'GariepKaroo', '94 (Gariep Karoo)'), + ( 95, 'Hobyograsslandsandshrublands', '95 (Hobyo grasslands and shrublands)'), + ( 96, 'IleEuropaandBassasdaIndiaxericscrub', '96 (Ile Europa and Bassas da India xeric scrub)'), + ( 97, 'Kalaharixericsavanna', '97 (Kalahari xeric savanna)'), + ( 98, 'Kaokovelddesert', '98 (Kaokoveld desert)'), + ( 99, 'Madagascarspinythickets', '99 (Madagascar spiny thickets)'), + (100, 'Madagascarsucculentwoodlands', '100 (Madagascar succulent woodlands)'), + (101, 'NamaKarooshrublands', '101 (Nama Karoo shrublands)'), + (102, 'NamaqualandRichtersveldsteppe', '102 (Namaqualand-Richtersveld steppe)'), + (103, 'NamibDesert', '103 (Namib Desert)'), + (104, 'Namibiansavannawoodlands', '104 (Namibian savanna woodlands)'), + (105, 'SocotraIslandxericshrublands', '105 (Socotra Island xeric shrublands)'), + (106, 'Somalimontanexericwoodlands', '106 (Somali montane xeric woodlands)'), + (107, 'SouthwestArabiancoastalxericshrublands', '107 (Southwest Arabian coastal xeric shrublands)'), + (108, 'SouthwestArabianEscarpmentshrublandsandwoodlands', '108 (Southwest Arabian Escarpment shrublands and woodlands)'), + (109, 'SouthwestArabianhighlandxericscrub', '109 (Southwest Arabian highland xeric scrub)'), + (110, 'SucculentKarooxericshrublands', '110 (Succulent Karoo xeric shrublands)'), + (111, 'CentralAfricanmangroves', '111 (Central African mangroves)'), + (112, 'EastAfricanmangroves', '112 (East African mangroves)'), + (113, 'Guineanmangroves', '113 (Guinean mangroves)'), + (114, 'Madagascarmangroves', '114 (Madagascar mangroves)'), + (115, 'RedSeamangroves', '115 (Red Sea mangroves)'), + (116, 'SouthernAfricamangroves', '116 (Southern Africa mangroves)'), + (117, 'AdelieLandtundra', '117 (Adelie Land tundra)'), + (118, 'CentralSouthAntarcticPeninsulatundra', '118 (Central South Antarctic Peninsula tundra)'), + (119, 'DronningMaudLandtundra', '119 (Dronning Maud Land tundra)'), + (120, 'EastAntarctictundra', '120 (East Antarctic tundra)'), + (121, 'EllsworthLandtundra', '121 (Ellsworth Land tundra)'), + (122, 'EllsworthMountainstundra', '122 (Ellsworth Mountains tundra)'), + (123, 'EnderbyLandtundra', '123 (Enderby Land tundra)'), + (124, 'MarieByrdLandtundra', '124 (Marie Byrd Land tundra)'), + (125, 'NorthVictoriaLandtundra', '125 (North Victoria Land tundra)'), + (126, 'NortheastAntarcticPeninsulatundra', '126 (Northeast Antarctic Peninsula tundra)'), + (127, 'NorthwestAntarcticPeninsulatundra', '127 (Northwest Antarctic Peninsula tundra)'), + (128, 'PrinceCharlesMountainstundra', '128 (Prince Charles Mountains tundra)'), + (129, 'ScotiaSeaIslandstundra', '129 (Scotia Sea Islands tundra)'), + (130, 'SouthAntarcticPeninsulatundra', '130 (South Antarctic Peninsula tundra)'), + (131, 'SouthOrkneyIslandstundra', '131 (South Orkney Islands tundra)'), + (132, 'SouthVictoriaLandtundra', '132 (South Victoria Land tundra)'), + (133, 'SouthernIndianOceanIslandstundra', '133 (Southern Indian Ocean Islands tundra)'), + (134, 'TransantarcticMountainstundra', '134 (Transantarctic Mountains tundra)'), + (135, 'AdmiraltyIslandslowlandrainforests', '135 (Admiralty Islands lowland rain forests)'), + (136, 'BandaSeaIslandsmoistdeciduousforests', '136 (Banda Sea Islands moist deciduous forests)'), + (137, 'BiakNumfoorrainforests', '137 (Biak-Numfoor rain forests)'), + (138, 'Bururainforests', '138 (Buru rain forests)'), + (139, 'CentralRangePapuanmontanerainforests', '139 (Central Range Papuan montane rain forests)'), + (140, 'Halmaherarainforests', '140 (Halmahera rain forests)'), + (141, 'HuonPeninsulamontanerainforests', '141 (Huon Peninsula montane rain forests)'), + (142, 'LordHoweIslandsubtropicalforests', '142 (Lord Howe Island subtropical forests)'), + (143, 'LouisiadeArchipelagorainforests', '143 (Louisiade Archipelago rain forests)'), + (144, 'NewBritainNewIrelandlowlandrainforests', '144 (New Britain-New Ireland lowland rain forests)'), + (145, 'NewBritainNewIrelandmontanerainforests', '145 (New Britain-New Ireland montane rain forests)'), + (146, 'NewCaledoniarainforests', '146 (New Caledonia rain forests)'), + (147, 'NorfolkIslandsubtropicalforests', '147 (Norfolk Island subtropical forests)'), + (148, 'NorthernNewGuinealowlandrainandfreshwaterswampforests', '148 (Northern New Guinea lowland rain and freshwater swamp forests)'), + (149, 'NorthernNewGuineamontanerainforests', '149 (Northern New Guinea montane rain forests)'), + (150, 'Queenslandtropicalrainforests', '150 (Queensland tropical rain forests)'), + (151, 'Seramrainforests', '151 (Seram rain forests)'), + (152, 'SolomonIslandsrainforests', '152 (Solomon Islands rain forests)'), + (153, 'SoutheastPapuanrainforests', '153 (Southeast Papuan rain forests)'), + (154, 'SouthernNewGuineafreshwaterswampforests', '154 (Southern New Guinea freshwater swamp forests)'), + (155, 'SouthernNewGuinealowlandrainforests', '155 (Southern New Guinea lowland rain forests)'), + (156, 'Sulawesilowlandrainforests', '156 (Sulawesi lowland rain forests)'), + (157, 'Sulawesimontanerainforests', '157 (Sulawesi montane rain forests)'), + (158, 'TrobriandIslandsrainforests', '158 (Trobriand Islands rain forests)'), + (159, 'Vanuaturainforests', '159 (Vanuatu rain forests)'), + (160, 'Vogelkopmontanerainforests', '160 (Vogelkop montane rain forests)'), + (161, 'VogelkopArulowlandrainforests', '161 (Vogelkop-Aru lowland rain forests)'), + (162, 'Yapenrainforests', '162 (Yapen rain forests)'), + (163, 'LesserSundasdeciduousforests', '163 (Lesser Sundas deciduous forests)'), + (164, 'NewCaledoniadryforests', '164 (New Caledonia dry forests)'), + (165, 'Sumbadeciduousforests', '165 (Sumba deciduous forests)'), + (166, 'TimorandWetardeciduousforests', '166 (Timor and Wetar deciduous forests)'), + (167, 'ChathamIslandtemperateforests', '167 (Chatham Island temperate forests)'), + (168, 'EasternAustraliantemperateforests', '168 (Eastern Australian temperate forests)'), + (169, 'Fiordlandtemperateforests', '169 (Fiordland temperate forests)'), + (170, 'NelsonCoasttemperateforests', '170 (Nelson Coast temperate forests)'), + (171, 'NewZealandNorthIslandtemperateforests', '171 (New Zealand North Island temperate forests)'), + (172, 'NewZealandSouthIslandtemperateforests', '172 (New Zealand South Island temperate forests)'), + (173, 'Northlandtemperatekauriforests', '173 (Northland temperate kauri forests)'), + (174, 'RakiuraIslandtemperateforests', '174 (Rakiura Island temperate forests)'), + (175, 'Richmondtemperateforests', '175 (Richmond temperate forests)'), + (176, 'SoutheastAustraliatemperateforests', '176 (Southeast Australia temperate forests)'), + (177, 'TasmanianCentralHighlandforests', '177 (Tasmanian Central Highland forests)'), + (178, 'Tasmaniantemperateforests', '178 (Tasmanian temperate forests)'), + (179, 'Tasmaniantemperaterainforests', '179 (Tasmanian temperate rain forests)'), + (180, 'Westlandtemperateforests', '180 (Westland temperate forests)'), + (181, 'ArnhemLandtropicalsavanna', '181 (Arnhem Land tropical savanna)'), + (182, 'Brigalowtropicalsavanna', '182 (Brigalow tropical savanna)'), + (183, 'CapeYorkPeninsulatropicalsavanna', '183 (Cape York Peninsula tropical savanna)'), + (184, 'Carpentariatropicalsavanna', '184 (Carpentaria tropical savanna)'), + (185, 'Einasleighuplandsavanna', '185 (Einasleigh upland savanna)'), + (186, 'Kimberlytropicalsavanna', '186 (Kimberly tropical savanna)'), + (187, 'MitchellGrassDowns', '187 (Mitchell Grass Downs)'), + (188, 'TransFlysavannaandgrasslands', '188 (Trans Fly savanna and grasslands)'), + (189, 'VictoriaPlainstropicalsavanna', '189 (Victoria Plains tropical savanna)'), + (190, 'CanterburyOtagotussockgrasslands', '190 (Canterbury-Otago tussock grasslands)'), + (191, 'EasternAustraliamulgashrublands', '191 (Eastern Australia mulga shrublands)'), + (192, 'SoutheastAustraliatemperatesavanna', '192 (Southeast Australia temperate savanna)'), + (193, 'AustralianAlpsmontanegrasslands', '193 (Australian Alps montane grasslands)'), + (194, 'NewZealandSouthIslandmontanegrasslands', '194 (New Zealand South Island montane grasslands)'), + (195, 'PapuanCentralRangesubalpinegrasslands', '195 (Papuan Central Range sub-alpine grasslands)'), + (196, 'AntipodesSubantarcticIslandstundra', '196 (Antipodes Subantarctic Islands tundra)'), + (197, 'Coolgardiewoodlands', '197 (Coolgardie woodlands)'), + (198, 'Esperancemallee', '198 (Esperance mallee)'), + (199, 'EyreandYorkmallee', '199 (Eyre and York mallee)'), + (200, 'FlindersLoftymontanewoodlands', '200 (Flinders-Lofty montane woodlands)'), + (201, 'Hamptonmalleeandwoodlands', '201 (Hampton mallee and woodlands)'), + (202, 'JarrahKarriforestandshrublands', '202 (Jarrah-Karri forest and shrublands)'), + (203, 'MurrayDarlingwoodlandsandmallee', '203 (Murray-Darling woodlands and mallee)'), + (204, 'Naracoortewoodlands', '204 (Naracoorte woodlands)'), + (205, 'SouthwestAustraliasavanna', '205 (Southwest Australia savanna)'), + (206, 'SouthwestAustraliawoodlands', '206 (Southwest Australia woodlands)'), + (207, 'Carnarvonxericshrublands', '207 (Carnarvon xeric shrublands)'), + (208, 'CentralRangesxericscrub', '208 (Central Ranges xeric scrub)'), + (209, 'Gibsondesert', '209 (Gibson desert)'), + (210, 'GreatSandyTanamidesert', '210 (Great Sandy-Tanami desert)'), + (211, 'GreatVictoriadesert', '211 (Great Victoria desert)'), + (212, 'NullarborPlainsxericshrublands', '212 (Nullarbor Plains xeric shrublands)'), + (213, 'Pilbarashrublands', '213 (Pilbara shrublands)'), + (214, 'Simpsondesert', '214 (Simpson desert)'), + (215, 'TirariSturtstonydesert', '215 (Tirari-Sturt stony desert)'), + (216, 'WesternAustralianMulgashrublands', '216 (Western Australian Mulga shrublands)'), + (217, 'NewGuineamangroves', '217 (New Guinea mangroves)'), + (218, 'AndamanIslandsrainforests', '218 (Andaman Islands rain forests)'), + (219, 'Borneolowlandrainforests', '219 (Borneo lowland rain forests)'), + (220, 'Borneomontanerainforests', '220 (Borneo montane rain forests)'), + (221, 'Borneopeatswampforests', '221 (Borneo peat swamp forests)'), + (222, 'BrahmaputraValleysemievergreenforests', '222 (Brahmaputra Valley semi-evergreen forests)'), + (223, 'CardamomMountainsrainforests', '223 (Cardamom Mountains rain forests)'), + (224, 'ChaoPhrayafreshwaterswampforests', '224 (Chao Phraya freshwater swamp forests)'), + (225, 'ChaoPhrayalowlandmoistdeciduousforests', '225 (Chao Phraya lowland moist deciduous forests)'), + (226, 'ChinHillsArakanYomamontaneforests', '226 (Chin Hills-Arakan Yoma montane forests)'), + (227, 'ChristmasandCocosIslandstropicalforests', '227 (Christmas and Cocos Islands tropical forests)'), + (228, 'EastDeccanmoistdeciduousforests', '228 (East Deccan moist deciduous forests)'), + (229, 'EasternJavaBalimontanerainforests', '229 (Eastern Java-Bali montane rain forests)'), + (230, 'EasternJavaBalirainforests', '230 (Eastern Java-Bali rain forests)'), + (231, 'GreaterNegrosPanayrainforests', '231 (Greater Negros-Panay rain forests)'), + (232, 'HainanIslandmonsoonrainforests', '232 (Hainan Island monsoon rain forests)'), + (233, 'Himalayansubtropicalbroadleafforests', '233 (Himalayan subtropical broadleaf forests)'), + (234, 'Irrawaddyfreshwaterswampforests', '234 (Irrawaddy freshwater swamp forests)'), + (235, 'Irrawaddymoistdeciduousforests', '235 (Irrawaddy moist deciduous forests)'), + (236, 'JianNansubtropicalevergreenforests', '236 (Jian Nan subtropical evergreen forests)'), + (237, 'KayahKarenmontanerainforests', '237 (Kayah-Karen montane rain forests)'), + (238, 'LowerGangeticPlainsmoistdeciduousforests', '238 (Lower Gangetic Plains moist deciduous forests)'), + (239, 'LuangPrabangmontanerainforests', '239 (Luang Prabang montane rain forests)'), + (240, 'Luzonmontanerainforests', '240 (Luzon montane rain forests)'), + (241, 'Luzonrainforests', '241 (Luzon rain forests)'), + (242, 'MalabarCoastmoistforests', '242 (Malabar Coast moist forests)'), + (243, 'MaldivesLakshadweepChagosArchipelagotropicalmoistforests', '243 (Maldives-Lakshadweep-Chagos Archipelago tropical moist forests)'), + (244, 'Meghalayasubtropicalforests', '244 (Meghalaya subtropical forests)'), + (245, 'MentawaiIslandsrainforests', '245 (Mentawai Islands rain forests)'), + (246, 'Mindanaomontanerainforests', '246 (Mindanao montane rain forests)'), + (247, 'MindanaoEasternVisayasrainforests', '247 (Mindanao-Eastern Visayas rain forests)'), + (248, 'Mindororainforests', '248 (Mindoro rain forests)'), + (249, 'MizoramManipurKachinrainforests', '249 (Mizoram-Manipur-Kachin rain forests)'), + (250, 'Myanmarcoastalrainforests', '250 (Myanmar coastal rain forests)'), + (251, 'NanseiIslandssubtropicalevergreenforests', '251 (Nansei Islands subtropical evergreen forests)'), + (252, 'NicobarIslandsrainforests', '252 (Nicobar Islands rain forests)'), + (253, 'NorthWesternGhatsmoistdeciduousforests', '253 (North Western Ghats moist deciduous forests)'), + (254, 'NorthWesternGhatsmontanerainforests', '254 (North Western Ghats montane rain forests)'), + (255, 'NorthernAnnamitesrainforests', '255 (Northern Annamites rain forests)'), + (256, 'NorthernIndochinasubtropicalforests', '256 (Northern Indochina subtropical forests)'), + (257, 'NorthernKhoratPlateaumoistdeciduousforests', '257 (Northern Khorat Plateau moist deciduous forests)'), + (258, 'NorthernThailandLaosmoistdeciduousforests', '258 (Northern Thailand-Laos moist deciduous forests)'), + (259, 'NorthernTrianglesubtropicalforests', '259 (Northern Triangle subtropical forests)'), + (260, 'NorthernVietnamlowlandrainforests', '260 (Northern Vietnam lowland rain forests)'), + (261, 'Orissasemievergreenforests', '261 (Orissa semi-evergreen forests)'), + (262, 'Palawanrainforests', '262 (Palawan rain forests)'), + (263, 'PeninsularMalaysianmontanerainforests', '263 (Peninsular Malaysian montane rain forests)'), + (264, 'PeninsularMalaysianpeatswampforests', '264 (Peninsular Malaysian peat swamp forests)'), + (265, 'PeninsularMalaysianrainforests', '265 (Peninsular Malaysian rain forests)'), + (266, 'RedRiverfreshwaterswampforests', '266 (Red River freshwater swamp forests)'), + (267, 'SouthChinaSeaIslands', '267 (South China Sea Islands)'), + (268, 'SouthChinaVietnamsubtropicalevergreenforests', '268 (South China-Vietnam subtropical evergreen forests)'), + (269, 'SouthTaiwanmonsoonrainforests', '269 (South Taiwan monsoon rain forests)'), + (270, 'SouthWesternGhatsmoistdeciduousforests', '270 (South Western Ghats moist deciduous forests)'), + (271, 'SouthWesternGhatsmontanerainforests', '271 (South Western Ghats montane rain forests)'), + (272, 'SouthernAnnamitesmontanerainforests', '272 (Southern Annamites montane rain forests)'), + (273, 'SouthwestBorneofreshwaterswampforests', '273 (Southwest Borneo freshwater swamp forests)'), + (274, 'SriLankalowlandrainforests', '274 (Sri Lanka lowland rain forests)'), + (275, 'SriLankamontanerainforests', '275 (Sri Lanka montane rain forests)'), + (276, 'SuluArchipelagorainforests', '276 (Sulu Archipelago rain forests)'), + (277, 'Sumatranfreshwaterswampforests', '277 (Sumatran freshwater swamp forests)'), + (278, 'Sumatranlowlandrainforests', '278 (Sumatran lowland rain forests)'), + (279, 'Sumatranmontanerainforests', '279 (Sumatran montane rain forests)'), + (280, 'Sumatranpeatswampforests', '280 (Sumatran peat swamp forests)'), + (281, 'Sundalandheathforests', '281 (Sundaland heath forests)'), + (282, 'Sundarbansfreshwaterswampforests', '282 (Sundarbans freshwater swamp forests)'), + (283, 'Taiwansubtropicalevergreenforests', '283 (Taiwan subtropical evergreen forests)'), + (284, 'TenasserimSouthThailandsemievergreenrainforests', '284 (Tenasserim-South Thailand semi-evergreen rain forests)'), + (285, 'TonleSapfreshwaterswampforests', '285 (Tonle Sap freshwater swamp forests)'), + (286, 'TonleSapMekongpeatswampforests', '286 (Tonle Sap-Mekong peat swamp forests)'), + (287, 'UpperGangeticPlainsmoistdeciduousforests', '287 (Upper Gangetic Plains moist deciduous forests)'), + (288, 'WesternJavamontanerainforests', '288 (Western Java montane rain forests)'), + (289, 'WesternJavarainforests', '289 (Western Java rain forests)'), + (290, 'CentralDeccanPlateaudrydeciduousforests', '290 (Central Deccan Plateau dry deciduous forests)'), + (291, 'CentralIndochinadryforests', '291 (Central Indochina dry forests)'), + (292, 'ChhotaNagpurdrydeciduousforests', '292 (Chhota-Nagpur dry deciduous forests)'), + (293, 'EastDeccandryevergreenforests', '293 (East Deccan dry-evergreen forests)'), + (294, 'Irrawaddydryforests', '294 (Irrawaddy dry forests)'), + (295, 'KhathiarGirdrydeciduousforests', '295 (Khathiar-Gir dry deciduous forests)'), + (296, 'NarmadaValleydrydeciduousforests', '296 (Narmada Valley dry deciduous forests)'), + (297, 'NorthDeccandrydeciduousforests', '297 (North Deccan dry deciduous forests)'), + (298, 'SouthDeccanPlateaudrydeciduousforests', '298 (South Deccan Plateau dry deciduous forests)'), + (299, 'SoutheastIndochinadryevergreenforests', '299 (Southeast Indochina dry evergreen forests)'), + (300, 'SouthernVietnamlowlanddryforests', '300 (Southern Vietnam lowland dry forests)'), + (301, 'SriLankadryzonedryevergreenforests', '301 (Sri Lanka dry-zone dry evergreen forests)'), + (302, 'Himalayansubtropicalpineforests', '302 (Himalayan subtropical pine forests)'), + (303, 'Luzontropicalpineforests', '303 (Luzon tropical pine forests)'), + (304, 'NortheastIndiaMyanmarpineforests', '304 (Northeast India-Myanmar pine forests)'), + (305, 'Sumatrantropicalpineforests', '305 (Sumatran tropical pine forests)'), + (306, 'EasternHimalayanbroadleafforests', '306 (Eastern Himalayan broadleaf forests)'), + (307, 'NorthernTriangletemperateforests', '307 (Northern Triangle temperate forests)'), + (308, 'WesternHimalayanbroadleafforests', '308 (Western Himalayan broadleaf forests)'), + (309, 'EasternHimalayansubalpineconiferforests', '309 (Eastern Himalayan subalpine conifer forests)'), + (310, 'WesternHimalayansubalpineconiferforests', '310 (Western Himalayan subalpine conifer forests)'), + (311, 'TeraiDuarsavannaandgrasslands', '311 (Terai-Duar savanna and grasslands)'), + (312, 'RannofKutchseasonalsaltmarsh', '312 (Rann of Kutch seasonal salt marsh)'), + (313, 'Kinabalumontanealpinemeadows', '313 (Kinabalu montane alpine meadows)'), + (314, 'Aravalliwestthornscrubforests', '314 (Aravalli west thorn scrub forests)'), + (315, 'Deccanthornscrubforests', '315 (Deccan thorn scrub forests)'), + (316, 'GodavariKrishnamangroves', '316 (Godavari-Krishna mangroves)'), + (317, 'IndusValleydesert', '317 (Indus Valley desert)'), + (318, 'Thardesert', '318 (Thar desert)'), + (319, 'Indochinamangroves', '319 (Indochina mangroves)'), + (320, 'IndusRiverDeltaArabianSeamangroves', '320 (Indus River Delta-Arabian Sea mangroves)'), + (321, 'MyanmarCoastmangroves', '321 (Myanmar Coast mangroves)'), + (322, 'SundaShelfmangroves', '322 (Sunda Shelf mangroves)'), + (323, 'Sundarbansmangroves', '323 (Sundarbans mangroves)'), + (324, 'SonoranSinaloansubtropicaldryforest', '324 (Sonoran-Sinaloan subtropical dry forest)'), + (325, 'Bermudasubtropicalconiferforests', '325 (Bermuda subtropical conifer forests)'), + (326, 'SierraMadreOccidentalpineoakforests', '326 (Sierra Madre Occidental pine-oak forests)'), + (327, 'SierraMadreOrientalpineoakforests', '327 (Sierra Madre Oriental pine-oak forests)'), + (328, 'AlleghenyHighlandsforests', '328 (Allegheny Highlands forests)'), + (329, 'Appalachianmixedmesophyticforests', '329 (Appalachian mixed mesophytic forests)'), + (330, 'AppalachianPiedmontforests', '330 (Appalachian Piedmont forests)'), + (331, 'AppalachianBlueRidgeforests', '331 (Appalachian-Blue Ridge forests)'), + (332, 'EastCentralTexasforests', '332 (East Central Texas forests)'), + (333, 'EasternCanadianForestBorealtransition', '333 (Eastern Canadian Forest-Boreal transition)'), + (334, 'EasternGreatLakeslowlandforests', '334 (Eastern Great Lakes lowland forests)'), + (335, 'GulfofStLawrencelowlandforests', '335 (Gulf of St. Lawrence lowland forests)'), + (336, 'InteriorPlateauUSHardwoodForests', '336 (Interior Plateau US Hardwood Forests)'), + (337, 'Mississippilowlandforests', '337 (Mississippi lowland forests)'), + (338, 'NewEnglandAcadianforests', '338 (New England-Acadian forests)'), + (339, 'NortheastUSCoastalforests', '339 (Northeast US Coastal forests)'), + (340, 'OzarkHighlandsmixedforests', '340 (Ozark Highlands mixed forests)'), + (341, 'OzarkMountainforests', '341 (Ozark Mountain forests)'), + (342, 'SouthernGreatLakesforests', '342 (Southern Great Lakes forests)'), + (343, 'UpperMidwestUSforestsavannatransition', '343 (Upper Midwest US forest-savanna transition)'), + (344, 'WesternGreatLakesforests', '344 (Western Great Lakes forests)'), + (345, 'AlbertaBritishColumbiafoothillsforests', '345 (Alberta-British Columbia foothills forests)'), + (346, 'ArizonaMountainsforests', '346 (Arizona Mountains forests)'), + (347, 'Atlanticcoastalpinebarrens', '347 (Atlantic coastal pine barrens)'), + (348, 'BlueMountainsforests', '348 (Blue Mountains forests)'), + (349, 'BritishColumbiacoastalconiferforests', '349 (British Columbia coastal conifer forests)'), + (350, 'CentralBritishColumbiaMountainforests', '350 (Central British Columbia Mountain forests)'), + (351, 'CentralPacificNorthwestcoastalforests', '351 (Central Pacific Northwest coastal forests)'), + (352, 'CentralSouthernCascadesForests', '352 (Central-Southern Cascades Forests)'), + (353, 'ColoradoRockiesforests', '353 (Colorado Rockies forests)'), + (354, 'EasternCascadesforests', '354 (Eastern Cascades forests)'), + (355, 'FraserPlateauandBasinconiferforests', '355 (Fraser Plateau and Basin conifer forests)'), + (356, 'GreatBasinmontaneforests', '356 (Great Basin montane forests)'), + (357, 'KlamathSiskiyouforests', '357 (Klamath-Siskiyou forests)'), + (358, 'NorthCascadesconiferforests', '358 (North Cascades conifer forests)'), + (359, 'NorthernCaliforniacoastalforests', '359 (Northern California coastal forests)'), + (360, 'NorthernPacificAlaskancoastalforests', '360 (Northern Pacific Alaskan coastal forests)'), + (361, 'NorthernRockiesconiferforests', '361 (Northern Rockies conifer forests)'), + (362, 'Okanogandryforests', '362 (Okanogan dry forests)'), + (363, 'PineyWoods', '363 (Piney Woods)'), + (364, 'Pugetlowlandforests', '364 (Puget lowland forests)'), + (365, 'QueenCharlotteIslandsconiferforests', '365 (Queen Charlotte Islands conifer forests)'), + (366, 'SierraNevadaforests', '366 (Sierra Nevada forests)'), + (367, 'SouthCentralRockiesforests', '367 (South Central Rockies forests)'), + (368, 'WasatchandUintamontaneforests', '368 (Wasatch and Uinta montane forests)'), + (369, 'AlaskaPeninsulamontanetaiga', '369 (Alaska Peninsula montane taiga)'), + (370, 'CentralCanadianShieldforests', '370 (Central Canadian Shield forests)'), + (371, 'CookInlettaiga', '371 (Cook Inlet taiga)'), + (372, 'CopperPlateautaiga', '372 (Copper Plateau taiga)'), + (373, 'EasternCanadianforests', '373 (Eastern Canadian forests)'), + (374, 'EasternCanadianShieldtaiga', '374 (Eastern Canadian Shield taiga)'), + (375, 'InteriorAlaskaYukonlowlandtaiga', '375 (Interior Alaska-Yukon lowland taiga)'), + (376, 'MidCanadaBorealPlainsforests', '376 (Mid-Canada Boreal Plains forests)'), + (377, 'MidwestCanadianShieldforests', '377 (Midwest Canadian Shield forests)'), + (378, 'MuskwaSlaveLaketaiga', '378 (Muskwa-Slave Lake taiga)'), + (379, 'NorthernCanadianShieldtaiga', '379 (Northern Canadian Shield taiga)'), + (380, 'NorthernCordilleraforests', '380 (Northern Cordillera forests)'), + (381, 'NorthwestTerritoriestaiga', '381 (Northwest Territories taiga)'), + (382, 'SouthernHudsonBaytaiga', '382 (Southern Hudson Bay taiga)'), + (383, 'WatsonHighlandstaiga', '383 (Watson Highlands taiga)'), + (384, 'WesternGulfcoastalgrasslands', '384 (Western Gulf coastal grasslands)'), + (385, 'CaliforniaCentralValleygrasslands', '385 (California Central Valley grasslands)'), + (386, 'CanadianAspenforestsandparklands', '386 (Canadian Aspen forests and parklands)'), + (387, 'CentralUSforestgrasslandstransition', '387 (Central US forest-grasslands transition)'), + (388, 'CentralTallgrassprairie', '388 (Central Tallgrass prairie)'), + (389, 'CentralSouthernUSmixedgrasslands', '389 (Central-Southern US mixed grasslands)'), + (390, 'CrossTimberssavannawoodland', '390 (Cross-Timbers savanna-woodland)'), + (391, 'EdwardsPlateausavanna', '391 (Edwards Plateau savanna)'), + (392, 'FlintHillstallgrassprairie', '392 (Flint Hills tallgrass prairie)'), + (393, 'MidAtlanticUScoastalsavannas', '393 (Mid-Atlantic US coastal savannas)'), + (394, 'MontanaValleyandFoothillgrasslands', '394 (Montana Valley and Foothill grasslands)'), + (395, 'NebraskaSandHillsmixedgrasslands', '395 (Nebraska Sand Hills mixed grasslands)'), + (396, 'NorthernShortgrassprairie', '396 (Northern Shortgrass prairie)'), + (397, 'NorthernTallgrassprairie', '397 (Northern Tallgrass prairie)'), + (398, 'Palouseprairie', '398 (Palouse prairie)'), + (399, 'SoutheastUSconifersavannas', '399 (Southeast US conifer savannas)'), + (400, 'SoutheastUSmixedwoodlandsandsavannas', '400 (Southeast US mixed woodlands and savannas)'), + (401, 'Texasblacklandprairies', '401 (Texas blackland prairies)'), + (402, 'Westernshortgrassprairie', '402 (Western shortgrass prairie)'), + (403, 'WillametteValleyoaksavanna', '403 (Willamette Valley oak savanna)'), + (404, 'AhklunandKilbuckUplandTundra', '404 (Ahklun and Kilbuck Upland Tundra)'), + (405, 'AlaskaStEliasRangetundra', '405 (Alaska-St. Elias Range tundra)'), + (406, 'AleutianIslandstundra', '406 (Aleutian Islands tundra)'), + (407, 'Arcticcoastaltundra', '407 (Arctic coastal tundra)'), + (408, 'Arcticfoothillstundra', '408 (Arctic foothills tundra)'), + (409, 'Beringialowlandtundra', '409 (Beringia lowland tundra)'), + (410, 'Beringiauplandtundra', '410 (Beringia upland tundra)'), + (411, 'BrooksBritishRangetundra', '411 (Brooks-British Range tundra)'), + (412, 'CanadianHighArctictundra', '412 (Canadian High Arctic tundra)'), + (413, 'CanadianLowArctictundra', '413 (Canadian Low Arctic tundra)'), + (414, 'CanadianMiddleArcticTundra', '414 (Canadian Middle Arctic Tundra)'), + (415, 'DavisHighlandstundra', '415 (Davis Highlands tundra)'), + (416, 'InteriorYukonAlaskaalpinetundra', '416 (Interior Yukon-Alaska alpine tundra)'), + (417, 'KalaallitNunaatArcticsteppe', '417 (Kalaallit Nunaat Arctic steppe)'), + (418, 'KalaallitNunaatHighArctictundra', '418 (Kalaallit Nunaat High Arctic tundra)'), + (419, 'OgilvieMacKenziealpinetundra', '419 (Ogilvie-MacKenzie alpine tundra)'), + (420, 'PacificCoastalMountainicefieldsandtundra', '420 (Pacific Coastal Mountain icefields and tundra)'), + (421, 'TorngatMountaintundra', '421 (Torngat Mountain tundra)'), + (422, 'Californiacoastalsageandchaparral', '422 (California coastal sage and chaparral)'), + (423, 'Californiainteriorchaparralandwoodlands', '423 (California interior chaparral and woodlands)'), + (424, 'Californiamontanechaparralandwoodlands', '424 (California montane chaparral and woodlands)'), + (425, 'SantaLuciaMontaneChaparralAndWoodlands', '425 (Santa Lucia Montane Chaparral & Woodlands)'), + (426, 'BajaCaliforniadesert', '426 (Baja California desert)'), + (427, 'CentralMexicanmatorral', '427 (Central Mexican matorral)'), + (428, 'Chihuahuandesert', '428 (Chihuahuan desert)'), + (429, 'ColoradoPlateaushrublands', '429 (Colorado Plateau shrublands)'), + (430, 'GreatBasinshrubsteppe', '430 (Great Basin shrub steppe)'), + (431, 'GulfofCaliforniaxericscrub', '431 (Gulf of California xeric scrub)'), + (432, 'MesetaCentralmatorral', '432 (Meseta Central matorral)'), + (433, 'Mojavedesert', '433 (Mojave desert)'), + (434, 'SnakeColumbiashrubsteppe', '434 (Snake-Columbia shrub steppe)'), + (435, 'Sonorandesert', '435 (Sonoran desert)'), + (436, 'Tamaulipanmatorral', '436 (Tamaulipan matorral)'), + (437, 'Tamaulipanmezquital', '437 (Tamaulipan mezquital)'), + (438, 'WyomingBasinshrubsteppe', '438 (Wyoming Basin shrub steppe)'), + (439, 'AltoParanaAtlanticforests', '439 (Alto Paraná Atlantic forests)'), + (440, 'Araucariamoistforests', '440 (Araucaria moist forests)'), + (441, 'AtlanticCoastrestingas', '441 (Atlantic Coast restingas)'), + (442, 'Bahiacoastalforests', '442 (Bahia coastal forests)'), + (443, 'Bahiainteriorforests', '443 (Bahia interior forests)'), + (444, 'BolivianYungas', '444 (Bolivian Yungas)'), + (445, 'CaatingaEnclavesmoistforests', '445 (Caatinga Enclaves moist forests)'), + (446, 'Caquetamoistforests', '446 (Caqueta moist forests)'), + (447, 'Catatumbomoistforests', '447 (Catatumbo moist forests)'), + (448, 'CaucaValleymontaneforests', '448 (Cauca Valley montane forests)'), + (449, 'CayosMiskitosSanAndresandProvidenciamoistforests', '449 (Cayos Miskitos-San Andrés and Providencia moist forests)'), + (450, 'CentralAmericanAtlanticmoistforests', '450 (Central American Atlantic moist forests)'), + (451, 'CentralAmericanmontaneforests', '451 (Central American montane forests)'), + (452, 'Chiapasmontaneforests', '452 (Chiapas montane forests)'), + (453, 'Chimalapasmontaneforests', '453 (Chimalapas montane forests)'), + (454, 'ChocoDarienmoistforests', '454 (Chocó-Darién moist forests)'), + (455, 'CocosIslandmoistforests', '455 (Cocos Island moist forests)'), + (456, 'CordilleraLaCostamontaneforests', '456 (Cordillera La Costa montane forests)'), + (457, 'CordilleraOrientalmontaneforests', '457 (Cordillera Oriental montane forests)'), + (458, 'CostaRicanseasonalmoistforests', '458 (Costa Rican seasonal moist forests)'), + (459, 'Cubanmoistforests', '459 (Cuban moist forests)'), + (460, 'EasternCordilleraRealmontaneforests', '460 (Eastern Cordillera Real montane forests)'), + (461, 'EasternPanamanianmontaneforests', '461 (Eastern Panamanian montane forests)'), + (462, 'FernandodeNoronhaAtoldasRocasmoistforests', '462 (Fernando de Noronha-Atol das Rocas moist forests)'), + (463, 'Guiananfreshwaterswampforests', '463 (Guianan freshwater swamp forests)'), + (464, 'GuiananHighlandsmoistforests', '464 (Guianan Highlands moist forests)'), + (465, 'Guiananlowlandmoistforests', '465 (Guianan lowland moist forests)'), + (466, 'Guiananpiedmontmoistforests', '466 (Guianan piedmont moist forests)'), + (467, 'Gurupavarzea', '467 (Gurupa várzea)'), + (468, 'Hispaniolanmoistforests', '468 (Hispaniolan moist forests)'), + (469, 'Iquitosvarzea', '469 (Iquitos várzea)'), + (470, 'IsthmianAtlanticmoistforests', '470 (Isthmian-Atlantic moist forests)'), + (471, 'IsthmianPacificmoistforests', '471 (Isthmian-Pacific moist forests)'), + (472, 'Jamaicanmoistforests', '472 (Jamaican moist forests)'), + (473, 'JapuraSolimoesNegromoistforests', '473 (Japurá-Solimões-Negro moist forests)'), + (474, 'JuruaPurusmoistforests', '474 (Juruá-Purus moist forests)'), + (475, 'LeewardIslandsmoistforests', '475 (Leeward Islands moist forests)'), + (476, 'MadeiraTapajosmoistforests', '476 (Madeira-Tapajós moist forests)'), + (477, 'MagdalenaValleymontaneforests', '477 (Magdalena Valley montane forests)'), + (478, 'MagdalenaUrabamoistforests', '478 (Magdalena-Urabá moist forests)'), + (479, 'Maranondryforests', '479 (Marañón dry forests)'), + (480, 'Marajovarzea', '480 (Marajó várzea)'), + (481, 'MatoGrossotropicaldryforests', '481 (Mato Grosso tropical dry forests)'), + (482, 'MonteAlegrevarzea', '482 (Monte Alegre várzea)'), + (483, 'Napomoistforests', '483 (Napo moist forests)'), + (484, 'NegroBrancomoistforests', '484 (Negro-Branco moist forests)'), + (485, 'NortheastBrazilrestingas', '485 (Northeast Brazil restingas)'), + (486, 'NorthwestAndeanmontaneforests', '486 (Northwest Andean montane forests)'), + (487, 'Oaxacanmontaneforests', '487 (Oaxacan montane forests)'), + (488, 'OrinocoDeltaswampforests', '488 (Orinoco Delta swamp forests)'), + (489, 'PantanosdeCentla', '489 (Pantanos de Centla)'), + (490, 'PantepuiforestsAndshrublands', '490 (Pantepui forests & shrublands)'), + (491, 'Pernambucocoastalforests', '491 (Pernambuco coastal forests)'), + (492, 'Pernambucointeriorforests', '492 (Pernambuco interior forests)'), + (493, 'PeruvianYungas', '493 (Peruvian Yungas)'), + (494, 'PetenVeracruzmoistforests', '494 (Petén-Veracruz moist forests)'), + (495, 'PuertoRicanmoistforests', '495 (Puerto Rican moist forests)'), + (496, 'Purusvarzea', '496 (Purus várzea)'), + (497, 'PurusMadeiramoistforests', '497 (Purus-Madeira moist forests)'), + (498, 'RioNegrocampinarana', '498 (Rio Negro campinarana)'), + (499, 'SantaMartamontaneforests', '499 (Santa Marta montane forests)'), + (500, 'SerradoMarcoastalforests', '500 (Serra do Mar coastal forests)'), + (501, 'SierradelosTuxtlas', '501 (Sierra de los Tuxtlas)'), + (502, 'SierraMadredeChiapasmoistforests', '502 (Sierra Madre de Chiapas moist forests)'), + (503, 'SolimoesJapuramoistforests', '503 (Solimões-Japurá moist forests)'), + (504, 'SouthernAndeanYungas', '504 (Southern Andean Yungas)'), + (505, 'SouthwestAmazonmoistforests', '505 (Southwest Amazon moist forests)'), + (506, 'Talamancanmontaneforests', '506 (Talamancan montane forests)'), + (507, 'TapajosXingumoistforests', '507 (Tapajós-Xingu moist forests)'), + (508, 'TocantinsPindaremoistforests', '508 (Tocantins/Pindare moist forests)'), + (509, 'TrindadeMartinVazIslandstropicalforests', '509 (Trindade-Martin Vaz Islands tropical forests)'), + (510, 'TrinidadandTobagomoistforest', '510 (Trinidad and Tobago moist forest)'), + (511, 'UatumaTrombetasmoistforests', '511 (Uatumã-Trombetas moist forests)'), + (512, 'Ucayalimoistforests', '512 (Ucayali moist forests)'), + (513, 'VenezuelanAndesmontaneforests', '513 (Venezuelan Andes montane forests)'), + (514, 'Veracruzmoistforests', '514 (Veracruz moist forests)'), + (515, 'Veracruzmontaneforests', '515 (Veracruz montane forests)'), + (516, 'WesternEcuadormoistforests', '516 (Western Ecuador moist forests)'), + (517, 'WindwardIslandsmoistforests', '517 (Windward Islands moist forests)'), + (518, 'XinguTocantinsAraguaiamoistforests', '518 (Xingu-Tocantins-Araguaia moist forests)'), + (519, 'Yucatanmoistforests', '519 (Yucatán moist forests)'), + (520, 'ApureVillavicenciodryforests', '520 (Apure-Villavicencio dry forests)'), + (521, 'Bajiodryforests', '521 (Bajío dry forests)'), + (522, 'Balsasdryforests', '522 (Balsas dry forests)'), + (523, 'Bolivianmontanedryforests', '523 (Bolivian montane dry forests)'), + (524, 'BrazilianAtlanticdryforests', '524 (Brazilian Atlantic dry forests)'), + (525, 'Caatinga', '525 (Caatinga)'), + (526, 'CaucaValleydryforests', '526 (Cauca Valley dry forests)'), + (527, 'CentralAmericandryforests', '527 (Central American dry forests)'), + (528, 'ChiapasDepressiondryforests', '528 (Chiapas Depression dry forests)'), + (529, 'Chiquitanodryforests', '529 (Chiquitano dry forests)'), + (530, 'Cubandryforests', '530 (Cuban dry forests)'), + (531, 'Ecuadoriandryforests', '531 (Ecuadorian dry forests)'), + (532, 'Hispaniolandryforests', '532 (Hispaniolan dry forests)'), + (533, 'IslasRevillagigedodryforests', '533 (Islas Revillagigedo dry forests)'), + (534, 'Jaliscodryforests', '534 (Jalisco dry forests)'), + (535, 'Jamaicandryforests', '535 (Jamaican dry forests)'), + (536, 'LaraFalcondryforests', '536 (Lara-Falcón dry forests)'), + (537, 'LesserAntilleandryforests', '537 (Lesser Antillean dry forests)'), + (538, 'MagdalenaValleydryforests', '538 (Magdalena Valley dry forests)'), + (539, 'Maracaibodryforests', '539 (Maracaibo dry forests)'), + (540, 'MaranhaoBabacuforests', '540 (Maranhão Babaçu forests)'), + (541, 'Panamaniandryforests', '541 (Panamanian dry forests)'), + (542, 'Patiavalleydryforests', '542 (Patía valley dry forests)'), + (543, 'PuertoRicandryforests', '543 (Puerto Rican dry forests)'), + (544, 'SierradelaLagunadryforests', '544 (Sierra de la Laguna dry forests)'), + (545, 'Sinaloandryforests', '545 (Sinaloan dry forests)'), + (546, 'SinuValleydryforests', '546 (Sinú Valley dry forests)'), + (547, 'SouthernPacificdryforests', '547 (Southern Pacific dry forests)'), + (548, 'TrinidadandTobagodryforest', '548 (Trinidad and Tobago dry forest)'), + (549, 'TumbesPiuradryforests', '549 (Tumbes-Piura dry forests)'), + (550, 'Veracruzdryforests', '550 (Veracruz dry forests)'), + (551, 'Yucatandryforests', '551 (Yucatán dry forests)'), + (552, 'Bahamianpineyards', '552 (Bahamian pineyards)'), + (553, 'CentralAmericanpineoakforests', '553 (Central American pine-oak forests)'), + (554, 'Cubanpineforests', '554 (Cuban pine forests)'), + (555, 'Hispaniolanpineforests', '555 (Hispaniolan pine forests)'), + (556, 'SierradelaLagunapineoakforests', '556 (Sierra de la Laguna pine-oak forests)'), + (557, 'SierraMadredeOaxacapineoakforests', '557 (Sierra Madre de Oaxaca pine-oak forests)'), + (558, 'SierraMadredelSurpineoakforests', '558 (Sierra Madre del Sur pine-oak forests)'), + (559, 'TransMexicanVolcanicBeltpineoakforests', '559 (Trans-Mexican Volcanic Belt pine-oak forests)'), + (560, 'JuanFernandezIslandstemperateforests', '560 (Juan Fernández Islands temperate forests)'), + (561, 'Magellanicsubpolarforests', '561 (Magellanic subpolar forests)'), + (562, 'SanFelixSanAmbrosioIslandstemperateforests', '562 (San Félix-San Ambrosio Islands temperate forests)'), + (563, 'Valdiviantemperateforests', '563 (Valdivian temperate forests)'), + (564, 'Belizianpinesavannas', '564 (Belizian pine savannas)'), + (565, 'Benisavanna', '565 (Beni savanna)'), + (566, 'CamposRupestresmontanesavanna', '566 (Campos Rupestres montane savanna)'), + (567, 'Cerrado', '567 (Cerrado)'), + (568, 'ClippertonIslandshrubandgrasslands', '568 (Clipperton Island shrub and grasslands)'), + (569, 'DryChaco', '569 (Dry Chaco)'), + (570, 'Guianansavanna', '570 (Guianan savanna)'), + (571, 'HumidChaco', '571 (Humid Chaco)'), + (572, 'Llanos', '572 (Llanos)'), + (573, 'Miskitopineforests', '573 (Miskito pine forests)'), + (574, 'Uruguayansavanna', '574 (Uruguayan savanna)'), + (575, 'Espinal', '575 (Espinal)'), + (576, 'HumidPampas', '576 (Humid Pampas)'), + (577, 'LowMonte', '577 (Low Monte)'), + (578, 'Patagoniansteppe', '578 (Patagonian steppe)'), + (579, 'Cubanwetlands', '579 (Cuban wetlands)'), + (580, 'Enriquillowetlands', '580 (Enriquillo wetlands)'), + (581, 'Evergladesfloodedgrasslands', '581 (Everglades flooded grasslands)'), + (582, 'Guayaquilfloodedgrasslands', '582 (Guayaquil flooded grasslands)'), + (583, 'Orinocowetlands', '583 (Orinoco wetlands)'), + (584, 'Pantanal', '584 (Pantanal)'), + (585, 'Paranafloodedsavanna', '585 (Paraná flooded savanna)'), + (586, 'SouthernConeMesopotamiansavanna', '586 (Southern Cone Mesopotamian savanna)'), + (587, 'CentralAndeandrypuna', '587 (Central Andean dry puna)'), + (588, 'CentralAndeanpuna', '588 (Central Andean puna)'), + (589, 'CentralAndeanwetpuna', '589 (Central Andean wet puna)'), + (590, 'CordilleraCentralparamo', '590 (Cordillera Central páramo)'), + (591, 'CordilleradeMeridaparamo', '591 (Cordillera de Merida páramo)'), + (592, 'HighMonte', '592 (High Monte)'), + (593, 'NorthernAndeanparamo', '593 (Northern Andean páramo)'), + (594, 'SantaMartaparamo', '594 (Santa Marta páramo)'), + (595, 'SouthernAndeansteppe', '595 (Southern Andean steppe)'), + (596, 'ChileanMatorral', '596 (Chilean Matorral)'), + (597, 'ArayaandPariaxericscrub', '597 (Araya and Paria xeric scrub)'), + (598, 'Atacamadesert', '598 (Atacama desert)'), + (599, 'Caribbeanshrublands', '599 (Caribbean shrublands)'), + (600, 'Cubancactusscrub', '600 (Cuban cactus scrub)'), + (601, 'GalapagosIslandsxericscrub', '601 (Galápagos Islands xeric scrub)'), + (602, 'GuajiraBarranquillaxericscrub', '602 (Guajira-Barranquilla xeric scrub)'), + (603, 'LaCostaxericshrublands', '603 (La Costa xeric shrublands)'), + (604, 'MalpeloIslandxericscrub', '604 (Malpelo Island xeric scrub)'), + (605, 'MotaguaValleythornscrub', '605 (Motagua Valley thornscrub)'), + (606, 'Paraguanaxericscrub', '606 (Paraguaná xeric scrub)'), + (607, 'SanLucanxericscrub', '607 (San Lucan xeric scrub)'), + (608, 'Sechuradesert', '608 (Sechura desert)'), + (609, 'StPeterandStPaulRocks', '609 (St. Peter and St. Paul Rocks)'), + (610, 'TehuacanValleymatorral', '610 (Tehuacán Valley matorral)'), + (611, 'AmazonOrinocoSouthernCaribbeanmangroves', '611 (Amazon-Orinoco-Southern Caribbean mangroves)'), + (612, 'BahamianAntilleanmangroves', '612 (Bahamian-Antillean mangroves)'), + (613, 'MesoamericanGulfCaribbeanmangroves', '613 (Mesoamerican Gulf-Caribbean mangroves)'), + (614, 'NorthernMesoamericanPacificmangroves', '614 (Northern Mesoamerican Pacific mangroves)'), + (615, 'SouthAmericanPacificmangroves', '615 (South American Pacific mangroves)'), + (616, 'SouthernAtlanticBrazilianmangroves', '616 (Southern Atlantic Brazilian mangroves)'), + (617, 'SouthernMesoamericanPacificmangroves', '617 (Southern Mesoamerican Pacific mangroves)'), + (618, 'Carolinestropicalmoistforests', '618 (Carolines tropical moist forests)'), + (619, 'CentralPolynesiantropicalmoistforests', '619 (Central Polynesian tropical moist forests)'), + (620, 'CookIslandstropicalmoistforests', '620 (Cook Islands tropical moist forests)'), + (621, 'EasternMicronesiatropicalmoistforests', '621 (Eastern Micronesia tropical moist forests)'), + (622, 'Fijitropicalmoistforests', '622 (Fiji tropical moist forests)'), + (623, 'Hawaiitropicalmoistforests', '623 (Hawai''i tropical moist forests)'), + (624, 'KermadecIslandssubtropicalmoistforests', '624 (Kermadec Islands subtropical moist forests)'), + (625, 'Marquesastropicalmoistforests', '625 (Marquesas tropical moist forests)'), + (626, 'Ogasawarasubtropicalmoistforests', '626 (Ogasawara subtropical moist forests)'), + (627, 'Palautropicalmoistforests', '627 (Palau tropical moist forests)'), + (628, 'RapaNuiandSalayGomezsubtropicalforests', '628 (Rapa Nui and Sala y Gómez subtropical forests)'), + (629, 'Samoantropicalmoistforests', '629 (Samoan tropical moist forests)'), + (630, 'SocietyIslandstropicalmoistforests', '630 (Society Islands tropical moist forests)'), + (631, 'Tongantropicalmoistforests', '631 (Tongan tropical moist forests)'), + (632, 'Tuamotutropicalmoistforests', '632 (Tuamotu tropical moist forests)'), + (633, 'Tubuaitropicalmoistforests', '633 (Tubuai tropical moist forests)'), + (634, 'WesternPolynesiantropicalmoistforests', '634 (Western Polynesian tropical moist forests)'), + (635, 'Fijitropicaldryforests', '635 (Fiji tropical dry forests)'), + (636, 'Hawaiitropicaldryforests', '636 (Hawai''i tropical dry forests)'), + (637, 'Marianastropicaldryforests', '637 (Marianas tropical dry forests)'), + (638, 'Yaptropicaldryforests', '638 (Yap tropical dry forests)'), + (639, 'Hawaiitropicalhighshrublands', '639 (Hawai''i tropical high shrublands)'), + (640, 'Hawaiitropicallowshrublands', '640 (Hawai''i tropical low shrublands)'), + (641, 'NorthwestHawaiiscrub', '641 (Northwest Hawai''i scrub)'), + (642, 'GuizhouPlateaubroadleafandmixedforests', '642 (Guizhou Plateau broadleaf and mixed forests)'), + (643, 'YunnanPlateausubtropicalevergreenforests', '643 (Yunnan Plateau subtropical evergreen forests)'), + (644, 'Appeninedeciduousmontaneforests', '644 (Appenine deciduous montane forests)'), + (645, 'Azorestemperatemixedforests', '645 (Azores temperate mixed forests)'), + (646, 'Balkanmixedforests', '646 (Balkan mixed forests)'), + (647, 'Balticmixedforests', '647 (Baltic mixed forests)'), + (648, 'Cantabrianmixedforests', '648 (Cantabrian mixed forests)'), + (649, 'CaspianHyrcanianmixedforests', '649 (Caspian Hyrcanian mixed forests)'), + (650, 'Caucasusmixedforests', '650 (Caucasus mixed forests)'), + (651, 'Celticbroadleafforests', '651 (Celtic broadleaf forests)'), + (652, 'CentralAnatoliansteppeandwoodlands', '652 (Central Anatolian steppe and woodlands)'), + (653, 'CentralChinaLoessPlateaumixedforests', '653 (Central China Loess Plateau mixed forests)'), + (654, 'CentralEuropeanmixedforests', '654 (Central European mixed forests)'), + (655, 'CentralKoreandeciduousforests', '655 (Central Korean deciduous forests)'), + (656, 'ChangbaiMountainsmixedforests', '656 (Changbai Mountains mixed forests)'), + (657, 'ChangjiangPlainevergreenforests', '657 (Changjiang Plain evergreen forests)'), + (658, 'CrimeanSubmediterraneanforestcomplex', '658 (Crimean Submediterranean forest complex)'), + (659, 'DabaMountainsevergreenforests', '659 (Daba Mountains evergreen forests)'), + (660, 'DinaricMountainsmixedforests', '660 (Dinaric Mountains mixed forests)'), + (661, 'EastEuropeanforeststeppe', '661 (East European forest steppe)'), + (662, 'EasternAnatoliandeciduousforests', '662 (Eastern Anatolian deciduous forests)'), + (663, 'EnglishLowlandsbeechforests', '663 (English Lowlands beech forests)'), + (664, 'EuropeanAtlanticmixedforests', '664 (European Atlantic mixed forests)'), + (665, 'EuxineColchicbroadleafforests', '665 (Euxine-Colchic broadleaf forests)'), + (666, 'Hokkaidodeciduousforests', '666 (Hokkaido deciduous forests)'), + (667, 'HuangHePlainmixedforests', '667 (Huang He Plain mixed forests)'), + (668, 'Madeiraevergreenforests', '668 (Madeira evergreen forests)'), + (669, 'Manchurianmixedforests', '669 (Manchurian mixed forests)'), + (670, 'Nihonkaievergreenforests', '670 (Nihonkai evergreen forests)'), + (671, 'Nihonkaimontanedeciduousforests', '671 (Nihonkai montane deciduous forests)'), + (672, 'NorthAtlanticmoistmixedforests', '672 (North Atlantic moist mixed forests)'), + (673, 'NortheastChinaPlaindeciduousforests', '673 (Northeast China Plain deciduous forests)'), + (674, 'Pannonianmixedforests', '674 (Pannonian mixed forests)'), + (675, 'PoBasinmixedforests', '675 (Po Basin mixed forests)'), + (676, 'Pyreneesconiferandmixedforests', '676 (Pyrenees conifer and mixed forests)'), + (677, 'QinLingMountainsdeciduousforests', '677 (Qin Ling Mountains deciduous forests)'), + (678, 'Rodopemontanemixedforests', '678 (Rodope montane mixed forests)'), + (679, 'Sarmaticmixedforests', '679 (Sarmatic mixed forests)'), + (680, 'SichuanBasinevergreenbroadleafforests', '680 (Sichuan Basin evergreen broadleaf forests)'), + (681, 'SouthernKoreaevergreenforests', '681 (Southern Korea evergreen forests)'), + (682, 'Taiheiyoevergreenforests', '682 (Taiheiyo evergreen forests)'), + (683, 'Taiheiyomontanedeciduousforests', '683 (Taiheiyo montane deciduous forests)'), + (684, 'TarimBasindeciduousforestsandsteppe', '684 (Tarim Basin deciduous forests and steppe)'), + (685, 'Ussuribroadleafandmixedforests', '685 (Ussuri broadleaf and mixed forests)'), + (686, 'WesternEuropeanbroadleafforests', '686 (Western European broadleaf forests)'), + (687, 'WesternSiberianhemiborealforests', '687 (Western Siberian hemiboreal forests)'), + (688, 'ZagrosMountainsforeststeppe', '688 (Zagros Mountains forest steppe)'), + (689, 'Alpsconiferandmixedforests', '689 (Alps conifer and mixed forests)'), + (690, 'Altaimontaneforestandforeststeppe', '690 (Altai montane forest and forest steppe)'), + (691, 'Caledonconiferforests', '691 (Caledon conifer forests)'), + (692, 'Carpathianmontaneforests', '692 (Carpathian montane forests)'), + (693, 'DaHingganDzhagdyMountainsconiferforests', '693 (Da Hinggan-Dzhagdy Mountains conifer forests)'), + (694, 'EastAfghanmontaneconiferforests', '694 (East Afghan montane conifer forests)'), + (695, 'ElburzRangeforeststeppe', '695 (Elburz Range forest steppe)'), + (696, 'Helanshanmontaneconiferforests', '696 (Helanshan montane conifer forests)'), + (697, 'HengduanMountainssubalpineconiferforests', '697 (Hengduan Mountains subalpine conifer forests)'), + (698, 'Hokkaidomontaneconiferforests', '698 (Hokkaido montane conifer forests)'), + (699, 'Honshualpineconiferforests', '699 (Honshu alpine conifer forests)'), + (700, 'KhangaiMountainsconiferforests', '700 (Khangai Mountains conifer forests)'), + (701, 'Mediterraneanconiferandmixedforests', '701 (Mediterranean conifer and mixed forests)'), + (702, 'NortheastHimalayansubalpineconiferforests', '702 (Northeast Himalayan subalpine conifer forests)'), + (703, 'NorthernAnatolianconiferanddeciduousforests', '703 (Northern Anatolian conifer and deciduous forests)'), + (704, 'NujiangLangcangGorgealpineconiferandmixedforests', '704 (Nujiang Langcang Gorge alpine conifer and mixed forests)'), + (705, 'QilianMountainsconiferforests', '705 (Qilian Mountains conifer forests)'), + (706, 'QionglaiMinshanconiferforests', '706 (Qionglai-Minshan conifer forests)'), + (707, 'Sayanmontaneconiferforests', '707 (Sayan montane conifer forests)'), + (708, 'Scandinaviancoastalconiferforests', '708 (Scandinavian coastal conifer forests)'), + (709, 'TianShanmontaneconiferforests', '709 (Tian Shan montane conifer forests)'), + (710, 'EastSiberiantaiga', '710 (East Siberian taiga)'), + (711, 'Icelandborealbirchforestsandalpinetundra', '711 (Iceland boreal birch forests and alpine tundra)'), + (712, 'Kamchatkataiga', '712 (Kamchatka taiga)'), + (713, 'KamchatkaKurilemeadowsandsparseforests', '713 (Kamchatka-Kurile meadows and sparse forests)'), + (714, 'NortheastSiberiantaiga', '714 (Northeast Siberian taiga)'), + (715, 'OkhotskManchuriantaiga', '715 (Okhotsk-Manchurian taiga)'), + (716, 'SakhalinIslandtaiga', '716 (Sakhalin Island taiga)'), + (717, 'ScandinavianandRussiantaiga', '717 (Scandinavian and Russian taiga)'), + (718, 'TransBaikalconiferforests', '718 (Trans-Baikal conifer forests)'), + (719, 'Uralsmontaneforestandtaiga', '719 (Urals montane forest and taiga)'), + (720, 'WestSiberiantaiga', '720 (West Siberian taiga)'), + (721, 'AlaiWesternTianShansteppe', '721 (Alai-Western Tian Shan steppe)'), + (722, 'AlHajarfoothillxericwoodlandsandshrublands', '722 (Al-Hajar foothill xeric woodlands and shrublands)'), + (723, 'AlHajarmontanewoodlandsandshrublands', '723 (Al-Hajar montane woodlands and shrublands)'), + (724, 'Altaisteppeandsemidesert', '724 (Altai steppe and semi-desert)'), + (725, 'CentralAnatoliansteppe', '725 (Central Anatolian steppe)'), + (726, 'Daurianforeststeppe', '726 (Daurian forest steppe)'), + (727, 'EasternAnatolianmontanesteppe', '727 (Eastern Anatolian montane steppe)'), + (728, 'EminValleysteppe', '728 (Emin Valley steppe)'), + (729, 'FaroeIslandsborealgrasslands', '729 (Faroe Islands boreal grasslands)'), + (730, 'GissaroAlaiopenwoodlands', '730 (Gissaro-Alai open woodlands)'), + (731, 'Kazakhforeststeppe', '731 (Kazakh forest steppe)'), + (732, 'Kazakhsteppe', '732 (Kazakh steppe)'), + (733, 'Kazakhuplandsteppe', '733 (Kazakh upland steppe)'), + (734, 'MongolianManchuriangrassland', '734 (Mongolian-Manchurian grassland)'), + (735, 'Ponticsteppe', '735 (Pontic steppe)'), + (736, 'SayanIntermontanesteppe', '736 (Sayan Intermontane steppe)'), + (737, 'SelengeOrkhonforeststeppe', '737 (Selenge-Orkhon forest steppe)'), + (738, 'SouthSiberianforeststeppe', '738 (South Siberian forest steppe)'), + (739, 'Syrianxericgrasslandsandshrublands', '739 (Syrian xeric grasslands and shrublands)'), + (740, 'TianShanfoothillaridsteppe', '740 (Tian Shan foothill arid steppe)'), + (741, 'Amurmeadowsteppe', '741 (Amur meadow steppe)'), + (742, 'BohaiSeasalinemeadow', '742 (Bohai Sea saline meadow)'), + (743, 'NenjiangRivergrassland', '743 (Nenjiang River grassland)'), + (744, 'NileDeltafloodedsavanna', '744 (Nile Delta flooded savanna)'), + (745, 'Saharanhalophytics', '745 (Saharan halophytics)'), + (746, 'SuiphunKhankameadowsandforestmeadows', '746 (Suiphun-Khanka meadows and forest meadows)'), + (747, 'TigrisEuphratesalluvialsaltmarsh', '747 (Tigris-Euphrates alluvial salt marsh)'), + (748, 'YellowSeasalinemeadow', '748 (Yellow Sea saline meadow)'), + (749, 'Altaialpinemeadowandtundra', '749 (Altai alpine meadow and tundra)'), + (750, 'CentralTibetanPlateaualpinesteppe', '750 (Central Tibetan Plateau alpine steppe)'), + (751, 'EasternHimalayanalpineshrubandmeadows', '751 (Eastern Himalayan alpine shrub and meadows)'), + (752, 'GhoratHazarajatalpinemeadow', '752 (Ghorat-Hazarajat alpine meadow)'), + (753, 'HinduKushalpinemeadow', '753 (Hindu Kush alpine meadow)'), + (754, 'KarakoramWestTibetanPlateaualpinesteppe', '754 (Karakoram-West Tibetan Plateau alpine steppe)'), + (755, 'KhangaiMountainsalpinemeadow', '755 (Khangai Mountains alpine meadow)'), + (756, 'KopetDagwoodlandsandforeststeppe', '756 (Kopet Dag woodlands and forest steppe)'), + (757, 'KuhRudandEasternIranmontanewoodlands', '757 (Kuh Rud and Eastern Iran montane woodlands)'), + (758, 'MediterraneanHighAtlasjunipersteppe', '758 (Mediterranean High Atlas juniper steppe)'), + (759, 'NorthTibetanPlateauKunlunMountainsalpinedesert', '759 (North Tibetan Plateau-Kunlun Mountains alpine desert)'), + (760, 'NorthwesternHimalayanalpineshrubandmeadows', '760 (Northwestern Himalayan alpine shrub and meadows)'), + (761, 'OrdosPlateausteppe', '761 (Ordos Plateau steppe)'), + (762, 'Pamiralpinedesertandtundra', '762 (Pamir alpine desert and tundra)'), + (763, 'QilianMountainssubalpinemeadows', '763 (Qilian Mountains subalpine meadows)'), + (764, 'Sayanalpinemeadowsandtundra', '764 (Sayan alpine meadows and tundra)'), + (765, 'SoutheastTibetshrublandsandmeadows', '765 (Southeast Tibet shrublands and meadows)'), + (766, 'SulaimanRangealpinemeadows', '766 (Sulaiman Range alpine meadows)'), + (767, 'TianShanmontanesteppeandmeadows', '767 (Tian Shan montane steppe and meadows)'), + (768, 'TibetanPlateaualpineshrublandsandmeadows', '768 (Tibetan Plateau alpine shrublands and meadows)'), + (769, 'WesternHimalayanalpineshrubandmeadows', '769 (Western Himalayan alpine shrub and meadows)'), + (770, 'YarlungZanboaridsteppe', '770 (Yarlung Zanbo arid steppe)'), + (771, 'CherskiiKolymamountaintundra', '771 (Cherskii-Kolyma mountain tundra)'), + (772, 'ChukchiPeninsulatundra', '772 (Chukchi Peninsula tundra)'), + (773, 'Kamchatkatundra', '773 (Kamchatka tundra)'), + (774, 'KolaPeninsulatundra', '774 (Kola Peninsula tundra)'), + (775, 'NortheastSiberiancoastaltundra', '775 (Northeast Siberian coastal tundra)'), + (776, 'NorthwestRussianNovayaZemlyatundra', '776 (Northwest Russian-Novaya Zemlya tundra)'), + (777, 'NovosibirskIslandsArcticdesert', '777 (Novosibirsk Islands Arctic desert)'), + (778, 'RussianArcticdesert', '778 (Russian Arctic desert)'), + (779, 'RussianBeringtundra', '779 (Russian Bering tundra)'), + (780, 'ScandinavianMontaneBirchforestandgrasslands', '780 (Scandinavian Montane Birch forest and grasslands)'), + (781, 'TaimyrCentralSiberiantundra', '781 (Taimyr-Central Siberian tundra)'), + (782, 'TransBaikalBaldMountaintundra', '782 (Trans-Baikal Bald Mountain tundra)'), + (783, 'WrangelIslandArcticdesert', '783 (Wrangel Island Arctic desert)'), + (784, 'YamalGydantundra', '784 (Yamal-Gydan tundra)'), + (785, 'AegeanandWesternTurkeysclerophyllousandmixedforests', '785 (Aegean and Western Turkey sclerophyllous and mixed forests)'), + (786, 'Anatolianconiferanddeciduousmixedforests', '786 (Anatolian conifer and deciduous mixed forests)'), + (787, 'CanaryIslandsdrywoodlandsandforests', '787 (Canary Islands dry woodlands and forests)'), + (788, 'Corsicanmontanebroadleafandmixedforests', '788 (Corsican montane broadleaf and mixed forests)'), + (789, 'CreteMediterraneanforests', '789 (Crete Mediterranean forests)'), + (790, 'CyprusMediterraneanforests', '790 (Cyprus Mediterranean forests)'), + (791, 'EasternMediterraneanconiferbroadleafforests', '791 (Eastern Mediterranean conifer-broadleaf forests)'), + (792, 'Iberianconiferforests', '792 (Iberian conifer forests)'), + (793, 'Iberiansclerophyllousandsemideciduousforests', '793 (Iberian sclerophyllous and semi-deciduous forests)'), + (794, 'Illyriandeciduousforests', '794 (Illyrian deciduous forests)'), + (795, 'Italiansclerophyllousandsemideciduousforests', '795 (Italian sclerophyllous and semi-deciduous forests)'), + (796, 'MediterraneanAcaciaArganiadrywoodlandsandsucculentthickets', '796 (Mediterranean Acacia-Argania dry woodlands and succulent thickets)'), + (797, 'Mediterraneandrywoodlandsandsteppe', '797 (Mediterranean dry woodlands and steppe)'), + (798, 'Mediterraneanwoodlandsandforests', '798 (Mediterranean woodlands and forests)'), + (799, 'NortheastSpainandSouthernFranceMediterraneanforests', '799 (Northeast Spain and Southern France Mediterranean forests)'), + (800, 'NorthwestIberianmontaneforests', '800 (Northwest Iberian montane forests)'), + (801, 'PindusMountainsmixedforests', '801 (Pindus Mountains mixed forests)'), + (802, 'SouthApenninemixedmontaneforests', '802 (South Apennine mixed montane forests)'), + (803, 'SoutheastIberianshrubsandwoodlands', '803 (Southeast Iberian shrubs and woodlands)'), + (804, 'SouthernAnatolianmontaneconiferanddeciduousforests', '804 (Southern Anatolian montane conifer and deciduous forests)'), + (805, 'SouthwestIberianMediterraneansclerophyllousandmixedforests', '805 (Southwest Iberian Mediterranean sclerophyllous and mixed forests)'), + (806, 'TyrrhenianAdriaticsclerophyllousandmixedforests', '806 (Tyrrhenian-Adriatic sclerophyllous and mixed forests)'), + (807, 'AfghanMountainssemidesert', '807 (Afghan Mountains semi-desert)'), + (808, 'AlashanPlateausemidesert', '808 (Alashan Plateau semi-desert)'), + (809, 'Arabiandesert', '809 (Arabian desert)'), + (810, 'Arabiansanddesert', '810 (Arabian sand desert)'), + (811, 'ArabianPersianGulfcoastalplaindesert', '811 (Arabian-Persian Gulf coastal plain desert)'), + (812, 'Azerbaijanshrubdesertandsteppe', '812 (Azerbaijan shrub desert and steppe)'), + (813, 'BadghyzandKarabilsemidesert', '813 (Badghyz and Karabil semi-desert)'), + (814, 'Baluchistanxericwoodlands', '814 (Baluchistan xeric woodlands)'), + (815, 'Caspianlowlanddesert', '815 (Caspian lowland desert)'), + (816, 'CentralAfghanMountainsxericwoodlands', '816 (Central Afghan Mountains xeric woodlands)'), + (817, 'CentralAsiannortherndesert', '817 (Central Asian northern desert)'), + (818, 'CentralAsianriparianwoodlands', '818 (Central Asian riparian woodlands)'), + (819, 'CentralAsiansoutherndesert', '819 (Central Asian southern desert)'), + (820, 'CentralPersiandesertbasins', '820 (Central Persian desert basins)'), + (821, 'EastArabianfogshrublandsandsanddesert', '821 (East Arabian fog shrublands and sand desert)'), + (822, 'EastSaharaDesert', '822 (East Sahara Desert)'), + (823, 'EastSaharanmontanexericwoodlands', '823 (East Saharan montane xeric woodlands)'), + (824, 'EasternGobidesertsteppe', '824 (Eastern Gobi desert steppe)'), + (825, 'GobiLakesValleydesertsteppe', '825 (Gobi Lakes Valley desert steppe)'), + (826, 'GreatLakesBasindesertsteppe', '826 (Great Lakes Basin desert steppe)'), + (827, 'JunggarBasinsemidesert', '827 (Junggar Basin semi-desert)'), + (828, 'Kazakhsemidesert', '828 (Kazakh semi-desert)'), + (829, 'KopetDagsemidesert', '829 (Kopet Dag semi-desert)'), + (830, 'Mesopotamianshrubdesert', '830 (Mesopotamian shrub desert)'), + (831, 'NorthArabiandesert', '831 (North Arabian desert)'), + (832, 'NorthArabianhighlandshrublands', '832 (North Arabian highland shrublands)'), + (833, 'NorthSaharanXericSteppeandWoodland', '833 (North Saharan Xeric Steppe and Woodland)'), + (834, 'Paropamisusxericwoodlands', '834 (Paropamisus xeric woodlands)'), + (835, 'QaidamBasinsemidesert', '835 (Qaidam Basin semi-desert)'), + (836, 'RedSeacoastaldesert', '836 (Red Sea coastal desert)'), + (837, 'RedSeaArabianDesertshrublands', '837 (Red Sea-Arabian Desert shrublands)'), + (838, 'RegistanNorthPakistansandydesert', '838 (Registan-North Pakistan sandy desert)'), + (839, 'SaharanAtlanticcoastaldesert', '839 (Saharan Atlantic coastal desert)'), + (840, 'SouthArabianplainsandplateaudesert', '840 (South Arabian plains and plateau desert)'), + (841, 'SouthIranNuboSindiandesertandsemidesert', '841 (South Iran Nubo-Sindian desert and semi-desert)'), + (842, 'SouthSaharadesert', '842 (South Sahara desert)'), + (843, 'Taklimakandesert', '843 (Taklimakan desert)'), + (844, 'TibestiJebelUweinatmontanexericwoodlands', '844 (Tibesti-Jebel Uweinat montane xeric woodlands)'), + (845, 'WestSaharadesert', '845 (West Sahara desert)'), + (846, 'WestSaharanmontanexericwoodlands', '846 (West Saharan montane xeric woodlands)'); + +-- Result Types + +CREATE TABLE IF NOT EXISTS RT_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT rt_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO RT_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'String', 'str'), + (1, 'Integer', 'int'), + (2, 'Float', 'float'); + +-- Data +-- ==== + +-- Data Flags + +CREATE TABLE IF NOT EXISTS DF_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT df_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO DF_vocabulary (enum_val, enum_str, enum_display_str) VALUES + ( 0, 'OKValidatedVerified', 'OK validated verified'), + ( 1, 'OKValidatedQCPassed', 'OK validated QC passed'), + ( 2, 'OKValidatedModified', 'OK validated modified'), + ( 3, 'OKPreliminaryVerified', 'OK preliminary verified'), + ( 4, 'OKPreliminaryQCPassed', 'OK preliminary QC passed'), + ( 5, 'OKPreliminaryModified', 'OK preliminary modified'), + ( 6, 'OKEstimated', 'OK estimated'), + ( 7, 'OKPreliminaryNotChecked', 'OK preliminary not checked'), + ( 10, 'QuestionableValidatedConfirmed', 'questionable validated confirmed'), + ( 11, 'QuestionableValidatedUnconfirmed', 'questionable validated unconfirmed'), + ( 12, 'QuestionableValidatedFlagged', 'questionable validated flagged'), + ( 13, 'QuestionablePreliminaryConfirmed', 'questionable preliminary confirmed'), + ( 14, 'QuestionablePreliminaryUnconfirmed', 'questionable preliminary unconfirmed'), + ( 15, 'QuestionablePreliminaryFlagged', 'questionable preliminary flagged'), + ( 16, 'QuestionablePreliminaryNotChecked', 'questionable preliminary not checked'), + ( 20, 'ErroneousValidatedConfirmed', 'erroneous validated confirmed'), + ( 21, 'ErroneousValidatedUnconfirmed', 'erroneous validated unconfirmed'), + ( 22, 'ErroneousValidatedFlagged1', 'erroneous validated flagged (1)'), + ( 23, 'ErroneousValidatedFlagged2', 'erroneous validated flagged (2)'), + ( 24, 'ErroneousPreliminaryConfirmed', 'erroneous preliminary confirmed'), + ( 25, 'ErroneousPreliminaryUnconfirmed', 'erroneous preliminary unconfirmed'), + ( 26, 'ErroneousPreliminaryFlagged1', 'erroneous preliminary flagged (1)'), + ( 27, 'ErroneousPreliminaryFlagged2', 'erroneous preliminary flagged (2)'), + ( 28, 'ErroneousPreliminaryNotChecked', 'erroneous preliminary not checked'), + ( 90, 'MissingValue', 'missing value'), + ( 91, 'UnknownQualityStatus', 'unknown quality status'), + (100, 'AllOK', 'all OK'), + (101, 'ValidatedOK', 'validated OK'), + (102, 'PreliminaryOK', 'preliminary OK'), + (103, 'NotModifiedOK', 'not modified OK'), + (104, 'ModifiedOK', 'modified OK'), + (110, 'AllQuestionable', 'all questionable'), + (111, 'ValidatedQuestionable', 'validated questionable'), + (112, 'PreliminaryQuestionable', 'preliminary questionable'), + (120, 'AllErroneous', 'all erroneous'), + (121, 'ValidatedErroneous', 'validated erroneous'), + (122, 'PreliminaryErroneous', 'preliminary erroneous'), + (130, 'AllQuestionableOrErroneous', 'all questionable or erroneous'), + (131, 'ValidatedQuestionableOrErroneous', 'validated questionable or erroneous'), + (132, 'PreliminaryQuestionableOrErroneous', 'preliminary questionable or erroneous'), + (140, 'NotChecked', 'not checked'); + + +-- Countries +-- ========= + +-- Country names + +CREATE TABLE IF NOT EXISTS CN_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT cn_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO CN_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (-1, 'N/A', 'N/A'), + (0, 'AF', 'Afghanistan'), + (1, 'AX', 'Åland Islands'), + (2, 'AL', 'Albania'), + (3, 'DZ', 'Algeria'), + (4, 'AS', 'American Samoa'), + (5, 'AD', 'Andorra'), + (6, 'AO', 'Angola'), + (7, 'AI', 'Anguilla'), + (8, 'AQ', 'Antarctica'), + (9, 'AG', 'Antigua and Barbuda'), + (10, 'AR', 'Argentina'), + (11, 'AM', 'Armenia'), + (12, 'AW', 'Aruba'), + (13, 'AU', 'Australia'), + (14, 'AT', 'Austria'), + (15, 'AZ', 'Azerbaijan'), + (16, 'BS', 'Bahamas'), + (17, 'BH', 'Bahrain'), + (18, 'BD', 'Bangladesh'), + (19, 'BB', 'Barbados'), + (20, 'BY', 'Belarus'), + (21, 'BE', 'Belgium'), + (22, 'BZ', 'Belize'), + (23, 'BJ', 'Benin'), + (24, 'BM', 'Bermuda'), + (25, 'BT', 'Bhutan'), + (26, 'BO', 'Plurinational State of Bolivia'), + (27, 'BQ', 'Bonaire, Sint Eustatius and Saba'), + (28, 'BA', 'Bosnia and Herzegovina'), + (29, 'BW', 'Botswana'), + (30, 'BV', 'Bouvet Island'), + (31, 'BR', 'Brazil'), + (32, 'IO', 'British Indian Ocean Territory'), + (33, 'UM', 'United States Minor Outlying Islands'), + (34, 'VG', 'British Virgin Islands'), + (35, 'VI', 'U.S. Virgin Islands'), + (36, 'BN', 'Brunei Darussalam'), + (37, 'BG', 'Bulgaria'), + (38, 'BF', 'Burkina Faso'), + (39, 'BI', 'Burundi'), + (40, 'KH', 'Cambodia'), + (41, 'CM', 'Cameroon'), + (42, 'CA', 'Canada'), + (43, 'CV', 'Cabo Verde'), + (44, 'KY', 'Cayman Islands'), + (45, 'CF', 'Central African Republic'), + (46, 'TD', 'Chad'), + (47, 'CL', 'Chile'), + (48, 'CN', 'China'), + (49, 'CX', 'Christmas Island'), + (50, 'CC', 'Cocos (Keeling) Islands'), + (51, 'CO', 'Colombia'), + (52, 'KM', 'Comoros'), + (53, 'CG', 'Congo'), + (54, 'CD', 'Democratic Republic of the Congo'), + (55, 'CK', 'Cook Islands'), + (56, 'CR', 'Costa Rica'), + (57, 'HR', 'Croatia'), + (58, 'CU', 'Cuba'), + (59, 'CW', 'Curaçao'), + (60, 'CY', 'Cyprus'), + (61, 'CZ', 'Czech Republic'), + (62, 'DK', 'Denmark'), + (63, 'DJ', 'Djibouti'), + (64, 'DM', 'Dominica'), + (65, 'DO', 'Dominican Republic'), + (66, 'EC', 'Ecuador'), + (67, 'EG', 'Egypt'), + (68, 'SV', 'El Salvador'), + (69, 'GQ', 'Equatorial Guinea'), + (70, 'ER', 'Eritrea'), + (71, 'EE', 'Estonia'), + (72, 'ET', 'Ethiopia'), + (73, 'FK', 'Falkland Islands (Malvinas)'), + (74, 'FO', 'Faroe Islands'), + (75, 'FJ', 'Fiji'), + (76, 'FI', 'Finland'), + (77, 'FR', 'France'), + (78, 'GF', 'French Guiana'), + (79, 'PF', 'French Polynesia'), + (80, 'TF', 'French Southern Territories'), + (81, 'GA', 'Gabon'), + (82, 'GM', 'Gambia'), + (83, 'GE', 'Georgia'), + (84, 'DE', 'Germany'), + (85, 'GH', 'Ghana'), + (86, 'GI', 'Gibraltar'), + (87, 'GR', 'Greece'), + (88, 'GL', 'Greenland'), + (89, 'GD', 'Grenada'), + (90, 'GP', 'Guadeloupe'), + (91, 'GU', 'Guam'), + (92, 'GT', 'Guatemala'), + (93, 'GG', 'Guernsey'), + (94, 'GN', 'Guinea'), + (95, 'GW', 'Guinea-Bissau'), + (96, 'GY', 'Guyana'), + (97, 'HT', 'Haiti'), + (98, 'HM', 'Heard Island and McDonald Islands'), + (99, 'VA', 'Holy See'), + (100, 'HN', 'Honduras'), + (101, 'HK', 'Hong Kong'), + (102, 'HU', 'Hungary'), + (103, 'IS', 'Iceland'), + (104, 'IN', 'India'), + (105, 'ID', 'Indonesia'), + (106, 'CI', 'Côte d''Ivoire'), + (107, 'IR', 'Islamic Republic of Iran'), + (108, 'IQ', 'Iraq'), + (109, 'IE', 'Ireland'), + (110, 'IM', 'Isle of Man'), + (111, 'IL', 'Israel'), + (112, 'IT', 'Italy'), + (113, 'JM', 'Jamaica'), + (114, 'JP', 'Japan'), + (115, 'JE', 'Jersey'), + (116, 'JO', 'Jordan'), + (117, 'KZ', 'Kazakhstan'), + (118, 'KE', 'Kenya'), + (119, 'KI', 'Kiribati'), + (120, 'KW', 'Kuwait'), + (121, 'KG', 'Kyrgyzstan'), + (122, 'LA', 'Lao People''s Democratic Republic'), + (123, 'LV', 'Latvia'), + (124, 'LB', 'Lebanon'), + (125, 'LS', 'Lesotho'), + (126, 'LR', 'Liberia'), + (127, 'LY', 'Libya'), + (128, 'LI', 'Liechtenstein'), + (129, 'LT', 'Lithuania'), + (130, 'LU', 'Luxembourg'), + (131, 'MO', 'Macao'), + (132, 'MK', 'North Macedonia'), + (133, 'MG', 'Madagascar'), + (134, 'MW', 'Malawi'), + (135, 'MY', 'Malaysia'), + (136, 'MV', 'Maldives'), + (137, 'ML', 'Mali'), + (138, 'MT', 'Malta'), + (139, 'MH', 'Marshall Islands'), + (140, 'MQ', 'Martinique'), + (141, 'MR', 'Mauritania'), + (142, 'MU', 'Mauritius'), + (143, 'YT', 'Mayotte'), + (144, 'MX', 'Mexico'), + (145, 'FM', 'Federated States of Micronesia'), + (146, 'MD', 'Republic of Moldova'), + (147, 'MC', 'Monaco'), + (148, 'MN', 'Mongolia'), + (149, 'ME', 'Montenegro'), + (150, 'MS', 'Montserrat'), + (151, 'MA', 'Morocco'), + (152, 'MZ', 'Mozambique'), + (153, 'MM', 'Myanmar'), + (154, 'NA', 'Namibia'), + (155, 'NR', 'Nauru'), + (156, 'NP', 'Nepal'), + (157, 'NL', 'Netherlands'), + (158, 'NC', 'New Caledonia'), + (159, 'NZ', 'New Zealand'), + (160, 'NI', 'Nicaragua'), + (161, 'NE', 'Niger'), + (162, 'NG', 'Nigeria'), + (163, 'NU', 'Niue'), + (164, 'NF', 'Norfolk Island'), + (165, 'KP', 'Democratic People''s Republic of Korea'), + (166, 'MP', 'Northern Mariana Islands'), + (167, 'NO', 'Norway'), + (168, 'OM', 'Oman'), + (169, 'PK', 'Pakistan'), + (170, 'PW', 'Palau'), + (171, 'PS', 'State of Palestine'), + (172, 'PA', 'Panama'), + (173, 'PG', 'Papua New Guinea'), + (174, 'PY', 'Paraguay'), + (175, 'PE', 'Peru'), + (176, 'PH', 'Philippines'), + (177, 'PN', 'Pitcairn'), + (178, 'PL', 'Poland'), + (179, 'PT', 'Portugal'), + (180, 'PR', 'Puerto Rico'), + (181, 'QA', 'Qatar'), + (182, 'XK', 'Republic of Kosovo'), + (183, 'RE', 'Réunion'), + (184, 'RO', 'Romania'), + (185, 'RU', 'Russian Federation'), + (186, 'RW', 'Rwanda'), + (187, 'BL', 'Saint Barthélemy'), + (188, 'SH', 'Saint Helena, Ascension and Tristan da Cunha'), + (189, 'KN', 'Saint Kitts and Nevis'), + (190, 'LC', 'Saint Lucia'), + (191, 'MF', 'Saint Martin (French part)'), + (192, 'PM', 'Saint Pierre and Miquelon'), + (193, 'VC', 'Saint Vincent and the Grenadines'), + (194, 'WS', 'Samoa'), + (195, 'SM', 'San Marino'), + (196, 'ST', 'Sao Tome and Principe'), + (197, 'SA', 'Saudi Arabia'), + (198, 'SN', 'Senegal'), + (199, 'RS', 'Serbia'), + (200, 'SC', 'Seychelles'), + (201, 'SL', 'Sierra Leone'), + (202, 'SG', 'Singapore'), + (203, 'SX', 'Sint Maarten (Dutch part)'), + (204, 'SK', 'Slovakia'), + (205, 'SI', 'Slovenia'), + (206, 'SB', 'Solomon Islands'), + (207, 'SO', 'Somalia'), + (208, 'ZA', 'South Africa'), + (209, 'GS', 'South Georgia and the South Sandwich Islands'), + (210, 'KR', 'Republic of Korea'), + (211, 'SS', 'South Sudan'), + (212, 'ES', 'Spain'), + (213, 'LK', 'Sri Lanka'), + (214, 'SD', 'Sudan'), + (215, 'SR', 'Suriname'), + (216, 'SJ', 'Svalbard and Jan Mayen'), + (217, 'SZ', 'Swaziland'), + (218, 'SE', 'Sweden'), + (219, 'CH', 'Switzerland'), + (220, 'SY', 'Syrian Arab Republic'), + (221, 'TW', 'Taiwan'), + (222, 'TJ', 'Tajikistan'), + (223, 'TZ', 'United Republic of Tanzania'), + (224, 'TH', 'Thailand'), + (225, 'TL', 'Timor-Leste'), + (226, 'TG', 'Togo'), + (227, 'TK', 'Tokelau'), + (228, 'TO', 'Tonga'), + (229, 'TT', 'Trinidad and Tobago'), + (230, 'TN', 'Tunisia'), + (231, 'TR', 'Turkey'), + (232, 'TM', 'Turkmenistan'), + (233, 'TC', 'Turks and Caicos Islands'), + (234, 'TV', 'Tuvalu'), + (235, 'UG', 'Uganda'), + (236, 'UA', 'Ukraine'), + (237, 'AE', 'United Arab Emirates'), + (238, 'GB', 'United Kingdom of Great Britain and Northern Ireland'), + (239, 'US', 'United States of America'), + (240, 'UY', 'Uruguay'), + (241, 'UZ', 'Uzbekistan'), + (242, 'VU', 'Vanuatu'), + (243, 'VE', 'Bolivarian Republic of Venezuela'), + (244, 'VN', 'Viet Nam'), + (245, 'WF', 'Wallis and Futuna'), + (246, 'EH', 'Western Sahara'), + (247, 'YE', 'Yemen'), + (248, 'ZM', 'Zambia'), + (249, 'ZW', 'Zimbabwe'); + +-- Timezones (from pytz) +-- ===================== + +CREATE TABLE IF NOT EXISTS TZ_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT tz_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO TZ_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (-1, 'N/A', 'N/A'), + (0, 'Africa/Abidjan', 'Africa/Abidjan'), + (1, 'Africa/Accra', 'Africa/Accra'), + (2, 'Africa/Addis_Ababa', 'Africa/Addis_Ababa'), + (3, 'Africa/Algiers', 'Africa/Algiers'), + (4, 'Africa/Asmara', 'Africa/Asmara'), + (5, 'Africa/Asmera', 'Africa/Asmera'), + (6, 'Africa/Bamako', 'Africa/Bamako'), + (7, 'Africa/Bangui', 'Africa/Bangui'), + (8, 'Africa/Banjul', 'Africa/Banjul'), + (9, 'Africa/Bissau', 'Africa/Bissau'), + (10, 'Africa/Blantyre', 'Africa/Blantyre'), + (11, 'Africa/Brazzaville', 'Africa/Brazzaville'), + (12, 'Africa/Bujumbura', 'Africa/Bujumbura'), + (13, 'Africa/Cairo', 'Africa/Cairo'), + (14, 'Africa/Casablanca', 'Africa/Casablanca'), + (15, 'Africa/Ceuta', 'Africa/Ceuta'), + (16, 'Africa/Conakry', 'Africa/Conakry'), + (17, 'Africa/Dakar', 'Africa/Dakar'), + (18, 'Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), + (19, 'Africa/Djibouti', 'Africa/Djibouti'), + (20, 'Africa/Douala', 'Africa/Douala'), + (21, 'Africa/El_Aaiun', 'Africa/El_Aaiun'), + (22, 'Africa/Freetown', 'Africa/Freetown'), + (23, 'Africa/Gaborone', 'Africa/Gaborone'), + (24, 'Africa/Harare', 'Africa/Harare'), + (25, 'Africa/Johannesburg', 'Africa/Johannesburg'), + (26, 'Africa/Juba', 'Africa/Juba'), + (27, 'Africa/Kampala', 'Africa/Kampala'), + (28, 'Africa/Khartoum', 'Africa/Khartoum'), + (29, 'Africa/Kigali', 'Africa/Kigali'), + (30, 'Africa/Kinshasa', 'Africa/Kinshasa'), + (31, 'Africa/Lagos', 'Africa/Lagos'), + (32, 'Africa/Libreville', 'Africa/Libreville'), + (33, 'Africa/Lome', 'Africa/Lome'), + (34, 'Africa/Luanda', 'Africa/Luanda'), + (35, 'Africa/Lubumbashi', 'Africa/Lubumbashi'), + (36, 'Africa/Lusaka', 'Africa/Lusaka'), + (37, 'Africa/Malabo', 'Africa/Malabo'), + (38, 'Africa/Maputo', 'Africa/Maputo'), + (39, 'Africa/Maseru', 'Africa/Maseru'), + (40, 'Africa/Mbabane', 'Africa/Mbabane'), + (41, 'Africa/Mogadishu', 'Africa/Mogadishu'), + (42, 'Africa/Monrovia', 'Africa/Monrovia'), + (43, 'Africa/Nairobi', 'Africa/Nairobi'), + (44, 'Africa/Ndjamena', 'Africa/Ndjamena'), + (45, 'Africa/Niamey', 'Africa/Niamey'), + (46, 'Africa/Nouakchott', 'Africa/Nouakchott'), + (47, 'Africa/Ouagadougou', 'Africa/Ouagadougou'), + (48, 'Africa/Porto-Novo', 'Africa/Porto-Novo'), + (49, 'Africa/Sao_Tome', 'Africa/Sao_Tome'), + (50, 'Africa/Timbuktu', 'Africa/Timbuktu'), + (51, 'Africa/Tripoli', 'Africa/Tripoli'), + (52, 'Africa/Tunis', 'Africa/Tunis'), + (53, 'Africa/Windhoek', 'Africa/Windhoek'), + (54, 'America/Adak', 'America/Adak'), + (55, 'America/Anchorage', 'America/Anchorage'), + (56, 'America/Anguilla', 'America/Anguilla'), + (57, 'America/Antigua', 'America/Antigua'), + (58, 'America/Araguaina', 'America/Araguaina'), + (59, 'America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), + (60, 'America/Argentina/Catamarca', 'America/Argentina/Catamarca'), + (61, 'America/Argentina/ComodRivadavia', 'America/Argentina/ComodRivadavia'), + (62, 'America/Argentina/Cordoba', 'America/Argentina/Cordoba'), + (63, 'America/Argentina/Jujuy', 'America/Argentina/Jujuy'), + (64, 'America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), + (65, 'America/Argentina/Mendoza', 'America/Argentina/Mendoza'), + (66, 'America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), + (67, 'America/Argentina/Salta', 'America/Argentina/Salta'), + (68, 'America/Argentina/San_Juan', 'America/Argentina/San_Juan'), + (69, 'America/Argentina/San_Luis', 'America/Argentina/San_Luis'), + (70, 'America/Argentina/Tucuman', 'America/Argentina/Tucuman'), + (71, 'America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), + (72, 'America/Aruba', 'America/Aruba'), + (73, 'America/Asuncion', 'America/Asuncion'), + (74, 'America/Atikokan', 'America/Atikokan'), + (75, 'America/Atka', 'America/Atka'), + (76, 'America/Bahia', 'America/Bahia'), + (77, 'America/Bahia_Banderas', 'America/Bahia_Banderas'), + (78, 'America/Barbados', 'America/Barbados'), + (79, 'America/Belem', 'America/Belem'), + (80, 'America/Belize', 'America/Belize'), + (81, 'America/Blanc-Sablon', 'America/Blanc-Sablon'), + (82, 'America/Boa_Vista', 'America/Boa_Vista'), + (83, 'America/Bogota', 'America/Bogota'), + (84, 'America/Boise', 'America/Boise'), + (85, 'America/Buenos_Aires', 'America/Buenos_Aires'), + (86, 'America/Cambridge_Bay', 'America/Cambridge_Bay'), + (87, 'America/Campo_Grande', 'America/Campo_Grande'), + (88, 'America/Cancun', 'America/Cancun'), + (89, 'America/Caracas', 'America/Caracas'), + (90, 'America/Catamarca', 'America/Catamarca'), + (91, 'America/Cayenne', 'America/Cayenne'), + (92, 'America/Cayman', 'America/Cayman'), + (93, 'America/Chicago', 'America/Chicago'), + (94, 'America/Chihuahua', 'America/Chihuahua'), + (95, 'America/Coral_Harbour', 'America/Coral_Harbour'), + (96, 'America/Cordoba', 'America/Cordoba'), + (97, 'America/Costa_Rica', 'America/Costa_Rica'), + (98, 'America/Creston', 'America/Creston'), + (99, 'America/Cuiaba', 'America/Cuiaba'), + (100, 'America/Curacao', 'America/Curacao'), + (101, 'America/Danmarkshavn', 'America/Danmarkshavn'), + (102, 'America/Dawson', 'America/Dawson'), + (103, 'America/Dawson_Creek', 'America/Dawson_Creek'), + (104, 'America/Denver', 'America/Denver'), + (105, 'America/Detroit', 'America/Detroit'), + (106, 'America/Dominica', 'America/Dominica'), + (107, 'America/Edmonton', 'America/Edmonton'), + (108, 'America/Eirunepe', 'America/Eirunepe'), + (109, 'America/El_Salvador', 'America/El_Salvador'), + (110, 'America/Ensenada', 'America/Ensenada'), + (111, 'America/Fort_Nelson', 'America/Fort_Nelson'), + (112, 'America/Fort_Wayne', 'America/Fort_Wayne'), + (113, 'America/Fortaleza', 'America/Fortaleza'), + (114, 'America/Glace_Bay', 'America/Glace_Bay'), + (115, 'America/Godthab', 'America/Godthab'), + (116, 'America/Goose_Bay', 'America/Goose_Bay'), + (117, 'America/Grand_Turk', 'America/Grand_Turk'), + (118, 'America/Grenada', 'America/Grenada'), + (119, 'America/Guadeloupe', 'America/Guadeloupe'), + (120, 'America/Guatemala', 'America/Guatemala'), + (121, 'America/Guayaquil', 'America/Guayaquil'), + (122, 'America/Guyana', 'America/Guyana'), + (123, 'America/Halifax', 'America/Halifax'), + (124, 'America/Havana', 'America/Havana'), + (125, 'America/Hermosillo', 'America/Hermosillo'), + (126, 'America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), + (127, 'America/Indiana/Knox', 'America/Indiana/Knox'), + (128, 'America/Indiana/Marengo', 'America/Indiana/Marengo'), + (129, 'America/Indiana/Petersburg', 'America/Indiana/Petersburg'), + (130, 'America/Indiana/Tell_City', 'America/Indiana/Tell_City'), + (131, 'America/Indiana/Vevay', 'America/Indiana/Vevay'), + (132, 'America/Indiana/Vincennes', 'America/Indiana/Vincennes'), + (133, 'America/Indiana/Winamac', 'America/Indiana/Winamac'), + (134, 'America/Indianapolis', 'America/Indianapolis'), + (135, 'America/Inuvik', 'America/Inuvik'), + (136, 'America/Iqaluit', 'America/Iqaluit'), + (137, 'America/Jamaica', 'America/Jamaica'), + (138, 'America/Jujuy', 'America/Jujuy'), + (139, 'America/Juneau', 'America/Juneau'), + (140, 'America/Kentucky/Louisville', 'America/Kentucky/Louisville'), + (141, 'America/Kentucky/Monticello', 'America/Kentucky/Monticello'), + (142, 'America/Knox_IN', 'America/Knox_IN'), + (143, 'America/Kralendijk', 'America/Kralendijk'), + (144, 'America/La_Paz', 'America/La_Paz'), + (145, 'America/Lima', 'America/Lima'), + (146, 'America/Los_Angeles', 'America/Los_Angeles'), + (147, 'America/Louisville', 'America/Louisville'), + (148, 'America/Lower_Princes', 'America/Lower_Princes'), + (149, 'America/Maceio', 'America/Maceio'), + (150, 'America/Managua', 'America/Managua'), + (151, 'America/Manaus', 'America/Manaus'), + (152, 'America/Marigot', 'America/Marigot'), + (153, 'America/Martinique', 'America/Martinique'), + (154, 'America/Matamoros', 'America/Matamoros'), + (155, 'America/Mazatlan', 'America/Mazatlan'), + (156, 'America/Mendoza', 'America/Mendoza'), + (157, 'America/Menominee', 'America/Menominee'), + (158, 'America/Merida', 'America/Merida'), + (159, 'America/Metlakatla', 'America/Metlakatla'), + (160, 'America/Mexico_City', 'America/Mexico_City'), + (161, 'America/Miquelon', 'America/Miquelon'), + (162, 'America/Moncton', 'America/Moncton'), + (163, 'America/Monterrey', 'America/Monterrey'), + (164, 'America/Montevideo', 'America/Montevideo'), + (165, 'America/Montreal', 'America/Montreal'), + (166, 'America/Montserrat', 'America/Montserrat'), + (167, 'America/Nassau', 'America/Nassau'), + (168, 'America/New_York', 'America/New_York'), + (169, 'America/Nipigon', 'America/Nipigon'), + (170, 'America/Nome', 'America/Nome'), + (171, 'America/Noronha', 'America/Noronha'), + (172, 'America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), + (173, 'America/North_Dakota/Center', 'America/North_Dakota/Center'), + (174, 'America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), + (175, 'America/Nuuk', 'America/Nuuk'), + (176, 'America/Ojinaga', 'America/Ojinaga'), + (177, 'America/Panama', 'America/Panama'), + (178, 'America/Pangnirtung', 'America/Pangnirtung'), + (179, 'America/Paramaribo', 'America/Paramaribo'), + (180, 'America/Phoenix', 'America/Phoenix'), + (181, 'America/Port-au-Prince', 'America/Port-au-Prince'), + (182, 'America/Port_of_Spain', 'America/Port_of_Spain'), + (183, 'America/Porto_Acre', 'America/Porto_Acre'), + (184, 'America/Porto_Velho', 'America/Porto_Velho'), + (185, 'America/Puerto_Rico', 'America/Puerto_Rico'), + (186, 'America/Punta_Arenas', 'America/Punta_Arenas'), + (187, 'America/Rainy_River', 'America/Rainy_River'), + (188, 'America/Rankin_Inlet', 'America/Rankin_Inlet'), + (189, 'America/Recife', 'America/Recife'), + (190, 'America/Regina', 'America/Regina'), + (191, 'America/Resolute', 'America/Resolute'), + (192, 'America/Rio_Branco', 'America/Rio_Branco'), + (193, 'America/Rosario', 'America/Rosario'), + (194, 'America/Santa_Isabel', 'America/Santa_Isabel'), + (195, 'America/Santarem', 'America/Santarem'), + (196, 'America/Santiago', 'America/Santiago'), + (197, 'America/Santo_Domingo', 'America/Santo_Domingo'), + (198, 'America/Sao_Paulo', 'America/Sao_Paulo'), + (199, 'America/Scoresbysund', 'America/Scoresbysund'), + (200, 'America/Shiprock', 'America/Shiprock'), + (201, 'America/Sitka', 'America/Sitka'), + (202, 'America/St_Barthelemy', 'America/St_Barthelemy'), + (203, 'America/St_Johns', 'America/St_Johns'), + (204, 'America/St_Kitts', 'America/St_Kitts'), + (205, 'America/St_Lucia', 'America/St_Lucia'), + (206, 'America/St_Thomas', 'America/St_Thomas'), + (207, 'America/St_Vincent', 'America/St_Vincent'), + (208, 'America/Swift_Current', 'America/Swift_Current'), + (209, 'America/Tegucigalpa', 'America/Tegucigalpa'), + (210, 'America/Thule', 'America/Thule'), + (211, 'America/Thunder_Bay', 'America/Thunder_Bay'), + (212, 'America/Tijuana', 'America/Tijuana'), + (213, 'America/Toronto', 'America/Toronto'), + (214, 'America/Tortola', 'America/Tortola'), + (215, 'America/Vancouver', 'America/Vancouver'), + (216, 'America/Virgin', 'America/Virgin'), + (217, 'America/Whitehorse', 'America/Whitehorse'), + (218, 'America/Winnipeg', 'America/Winnipeg'), + (219, 'America/Yakutat', 'America/Yakutat'), + (220, 'America/Yellowknife', 'America/Yellowknife'), + (221, 'Antarctica/Casey', 'Antarctica/Casey'), + (222, 'Antarctica/Davis', 'Antarctica/Davis'), + (223, 'Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), + (224, 'Antarctica/Macquarie', 'Antarctica/Macquarie'), + (225, 'Antarctica/Mawson', 'Antarctica/Mawson'), + (226, 'Antarctica/McMurdo', 'Antarctica/McMurdo'), + (227, 'Antarctica/Palmer', 'Antarctica/Palmer'), + (228, 'Antarctica/Rothera', 'Antarctica/Rothera'), + (229, 'Antarctica/South_Pole', 'Antarctica/South_Pole'), + (230, 'Antarctica/Syowa', 'Antarctica/Syowa'), + (231, 'Antarctica/Troll', 'Antarctica/Troll'), + (232, 'Antarctica/Vostok', 'Antarctica/Vostok'), + (233, 'Arctic/Longyearbyen', 'Arctic/Longyearbyen'), + (234, 'Asia/Aden', 'Asia/Aden'), + (235, 'Asia/Almaty', 'Asia/Almaty'), + (236, 'Asia/Amman', 'Asia/Amman'), + (237, 'Asia/Anadyr', 'Asia/Anadyr'), + (238, 'Asia/Aqtau', 'Asia/Aqtau'), + (239, 'Asia/Aqtobe', 'Asia/Aqtobe'), + (240, 'Asia/Ashgabat', 'Asia/Ashgabat'), + (241, 'Asia/Ashkhabad', 'Asia/Ashkhabad'), + (242, 'Asia/Atyrau', 'Asia/Atyrau'), + (243, 'Asia/Baghdad', 'Asia/Baghdad'), + (244, 'Asia/Bahrain', 'Asia/Bahrain'), + (245, 'Asia/Baku', 'Asia/Baku'), + (246, 'Asia/Bangkok', 'Asia/Bangkok'), + (247, 'Asia/Barnaul', 'Asia/Barnaul'), + (248, 'Asia/Beirut', 'Asia/Beirut'), + (249, 'Asia/Bishkek', 'Asia/Bishkek'), + (250, 'Asia/Brunei', 'Asia/Brunei'), + (251, 'Asia/Calcutta', 'Asia/Calcutta'), + (252, 'Asia/Chita', 'Asia/Chita'), + (253, 'Asia/Choibalsan', 'Asia/Choibalsan'), + (254, 'Asia/Chongqing', 'Asia/Chongqing'), + (255, 'Asia/Chungking', 'Asia/Chungking'), + (256, 'Asia/Colombo', 'Asia/Colombo'), + (257, 'Asia/Dacca', 'Asia/Dacca'), + (258, 'Asia/Damascus', 'Asia/Damascus'), + (259, 'Asia/Dhaka', 'Asia/Dhaka'), + (260, 'Asia/Dili', 'Asia/Dili'), + (261, 'Asia/Dubai', 'Asia/Dubai'), + (262, 'Asia/Dushanbe', 'Asia/Dushanbe'), + (263, 'Asia/Famagusta', 'Asia/Famagusta'), + (264, 'Asia/Gaza', 'Asia/Gaza'), + (265, 'Asia/Harbin', 'Asia/Harbin'), + (266, 'Asia/Hebron', 'Asia/Hebron'), + (267, 'Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), + (268, 'Asia/Hong_Kong', 'Asia/Hong_Kong'), + (269, 'Asia/Hovd', 'Asia/Hovd'), + (270, 'Asia/Irkutsk', 'Asia/Irkutsk'), + (271, 'Asia/Istanbul', 'Asia/Istanbul'), + (272, 'Asia/Jakarta', 'Asia/Jakarta'), + (273, 'Asia/Jayapura', 'Asia/Jayapura'), + (274, 'Asia/Jerusalem', 'Asia/Jerusalem'), + (275, 'Asia/Kabul', 'Asia/Kabul'), + (276, 'Asia/Kamchatka', 'Asia/Kamchatka'), + (277, 'Asia/Karachi', 'Asia/Karachi'), + (278, 'Asia/Kashgar', 'Asia/Kashgar'), + (279, 'Asia/Kathmandu', 'Asia/Kathmandu'), + (280, 'Asia/Katmandu', 'Asia/Katmandu'), + (281, 'Asia/Khandyga', 'Asia/Khandyga'), + (282, 'Asia/Kolkata', 'Asia/Kolkata'), + (283, 'Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), + (284, 'Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), + (285, 'Asia/Kuching', 'Asia/Kuching'), + (286, 'Asia/Kuwait', 'Asia/Kuwait'), + (287, 'Asia/Macao', 'Asia/Macao'), + (288, 'Asia/Macau', 'Asia/Macau'), + (289, 'Asia/Magadan', 'Asia/Magadan'), + (290, 'Asia/Makassar', 'Asia/Makassar'), + (291, 'Asia/Manila', 'Asia/Manila'), + (292, 'Asia/Muscat', 'Asia/Muscat'), + (293, 'Asia/Nicosia', 'Asia/Nicosia'), + (294, 'Asia/Novokuznetsk', 'Asia/Novokuznetsk'), + (295, 'Asia/Novosibirsk', 'Asia/Novosibirsk'), + (296, 'Asia/Omsk', 'Asia/Omsk'), + (297, 'Asia/Oral', 'Asia/Oral'), + (298, 'Asia/Phnom_Penh', 'Asia/Phnom_Penh'), + (299, 'Asia/Pontianak', 'Asia/Pontianak'), + (300, 'Asia/Pyongyang', 'Asia/Pyongyang'), + (301, 'Asia/Qatar', 'Asia/Qatar'), + (302, 'Asia/Qostanay', 'Asia/Qostanay'), + (303, 'Asia/Qyzylorda', 'Asia/Qyzylorda'), + (304, 'Asia/Rangoon', 'Asia/Rangoon'), + (305, 'Asia/Riyadh', 'Asia/Riyadh'), + (306, 'Asia/Saigon', 'Asia/Saigon'), + (307, 'Asia/Sakhalin', 'Asia/Sakhalin'), + (308, 'Asia/Samarkand', 'Asia/Samarkand'), + (309, 'Asia/Seoul', 'Asia/Seoul'), + (310, 'Asia/Shanghai', 'Asia/Shanghai'), + (311, 'Asia/Singapore', 'Asia/Singapore'), + (312, 'Asia/Srednekolymsk', 'Asia/Srednekolymsk'), + (313, 'Asia/Taipei', 'Asia/Taipei'), + (314, 'Asia/Tashkent', 'Asia/Tashkent'), + (315, 'Asia/Tbilisi', 'Asia/Tbilisi'), + (316, 'Asia/Tehran', 'Asia/Tehran'), + (317, 'Asia/Tel_Aviv', 'Asia/Tel_Aviv'), + (318, 'Asia/Thimbu', 'Asia/Thimbu'), + (319, 'Asia/Thimphu', 'Asia/Thimphu'), + (320, 'Asia/Tokyo', 'Asia/Tokyo'), + (321, 'Asia/Tomsk', 'Asia/Tomsk'), + (322, 'Asia/Ujung_Pandang', 'Asia/Ujung_Pandang'), + (323, 'Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), + (324, 'Asia/Ulan_Bator', 'Asia/Ulan_Bator'), + (325, 'Asia/Urumqi', 'Asia/Urumqi'), + (326, 'Asia/Ust-Nera', 'Asia/Ust-Nera'), + (327, 'Asia/Vientiane', 'Asia/Vientiane'), + (328, 'Asia/Vladivostok', 'Asia/Vladivostok'), + (329, 'Asia/Yakutsk', 'Asia/Yakutsk'), + (330, 'Asia/Yangon', 'Asia/Yangon'), + (331, 'Asia/Yekaterinburg', 'Asia/Yekaterinburg'), + (332, 'Asia/Yerevan', 'Asia/Yerevan'), + (333, 'Atlantic/Azores', 'Atlantic/Azores'), + (334, 'Atlantic/Bermuda', 'Atlantic/Bermuda'), + (335, 'Atlantic/Canary', 'Atlantic/Canary'), + (336, 'Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), + (337, 'Atlantic/Faeroe', 'Atlantic/Faeroe'), + (338, 'Atlantic/Faroe', 'Atlantic/Faroe'), + (339, 'Atlantic/Jan_Mayen', 'Atlantic/Jan_Mayen'), + (340, 'Atlantic/Madeira', 'Atlantic/Madeira'), + (341, 'Atlantic/Reykjavik', 'Atlantic/Reykjavik'), + (342, 'Atlantic/South_Georgia', 'Atlantic/South_Georgia'), + (343, 'Atlantic/St_Helena', 'Atlantic/St_Helena'), + (344, 'Atlantic/Stanley', 'Atlantic/Stanley'), + (345, 'Australia/ACT', 'Australia/ACT'), + (346, 'Australia/Adelaide', 'Australia/Adelaide'), + (347, 'Australia/Brisbane', 'Australia/Brisbane'), + (348, 'Australia/Broken_Hill', 'Australia/Broken_Hill'), + (349, 'Australia/Canberra', 'Australia/Canberra'), + (350, 'Australia/Currie', 'Australia/Currie'), + (351, 'Australia/Darwin', 'Australia/Darwin'), + (352, 'Australia/Eucla', 'Australia/Eucla'), + (353, 'Australia/Hobart', 'Australia/Hobart'), + (354, 'Australia/LHI', 'Australia/LHI'), + (355, 'Australia/Lindeman', 'Australia/Lindeman'), + (356, 'Australia/Lord_Howe', 'Australia/Lord_Howe'), + (357, 'Australia/Melbourne', 'Australia/Melbourne'), + (358, 'Australia/NSW', 'Australia/NSW'), + (359, 'Australia/North', 'Australia/North'), + (360, 'Australia/Perth', 'Australia/Perth'), + (361, 'Australia/Queensland', 'Australia/Queensland'), + (362, 'Australia/South', 'Australia/South'), + (363, 'Australia/Sydney', 'Australia/Sydney'), + (364, 'Australia/Tasmania', 'Australia/Tasmania'), + (365, 'Australia/Victoria', 'Australia/Victoria'), + (366, 'Australia/West', 'Australia/West'), + (367, 'Australia/Yancowinna', 'Australia/Yancowinna'), + (368, 'Brazil/Acre', 'Brazil/Acre'), + (369, 'Brazil/DeNoronha', 'Brazil/DeNoronha'), + (370, 'Brazil/East', 'Brazil/East'), + (371, 'Brazil/West', 'Brazil/West'), + (372, 'CET', 'CET'), + (373, 'CST6CDT', 'CST6CDT'), + (374, 'Canada/Atlantic', 'Canada/Atlantic'), + (375, 'Canada/Central', 'Canada/Central'), + (376, 'Canada/Eastern', 'Canada/Eastern'), + (377, 'Canada/Mountain', 'Canada/Mountain'), + (378, 'Canada/Newfoundland', 'Canada/Newfoundland'), + (379, 'Canada/Pacific', 'Canada/Pacific'), + (380, 'Canada/Saskatchewan', 'Canada/Saskatchewan'), + (381, 'Canada/Yukon', 'Canada/Yukon'), + (382, 'Chile/Continental', 'Chile/Continental'), + (383, 'Chile/EasterIsland', 'Chile/EasterIsland'), + (384, 'Cuba', 'Cuba'), + (385, 'EET', 'EET'), + (386, 'EST', 'EST'), + (387, 'EST5EDT', 'EST5EDT'), + (388, 'Egypt', 'Egypt'), + (389, 'Eire', 'Eire'), + (390, 'Etc/GMT', 'Etc/GMT'), + (391, 'Etc/GMT+0', 'Etc/GMT+0'), + (392, 'Etc/GMT+1', 'Etc/GMT+1'), + (393, 'Etc/GMT+10', 'Etc/GMT+10'), + (394, 'Etc/GMT+11', 'Etc/GMT+11'), + (395, 'Etc/GMT+12', 'Etc/GMT+12'), + (396, 'Etc/GMT+2', 'Etc/GMT+2'), + (397, 'Etc/GMT+3', 'Etc/GMT+3'), + (398, 'Etc/GMT+4', 'Etc/GMT+4'), + (399, 'Etc/GMT+5', 'Etc/GMT+5'), + (400, 'Etc/GMT+6', 'Etc/GMT+6'), + (401, 'Etc/GMT+7', 'Etc/GMT+7'), + (402, 'Etc/GMT+8', 'Etc/GMT+8'), + (403, 'Etc/GMT+9', 'Etc/GMT+9'), + (404, 'Etc/GMT-0', 'Etc/GMT-0'), + (405, 'Etc/GMT-1', 'Etc/GMT-1'), + (406, 'Etc/GMT-10', 'Etc/GMT-10'), + (407, 'Etc/GMT-11', 'Etc/GMT-11'), + (408, 'Etc/GMT-12', 'Etc/GMT-12'), + (409, 'Etc/GMT-13', 'Etc/GMT-13'), + (410, 'Etc/GMT-14', 'Etc/GMT-14'), + (411, 'Etc/GMT-2', 'Etc/GMT-2'), + (412, 'Etc/GMT-3', 'Etc/GMT-3'), + (413, 'Etc/GMT-4', 'Etc/GMT-4'), + (414, 'Etc/GMT-5', 'Etc/GMT-5'), + (415, 'Etc/GMT-6', 'Etc/GMT-6'), + (416, 'Etc/GMT-7', 'Etc/GMT-7'), + (417, 'Etc/GMT-8', 'Etc/GMT-8'), + (418, 'Etc/GMT-9', 'Etc/GMT-9'), + (419, 'Etc/GMT0', 'Etc/GMT0'), + (420, 'Etc/Greenwich', 'Etc/Greenwich'), + (421, 'Etc/UCT', 'Etc/UCT'), + (422, 'Etc/UTC', 'Etc/UTC'), + (423, 'Etc/Universal', 'Etc/Universal'), + (424, 'Etc/Zulu', 'Etc/Zulu'), + (425, 'Europe/Amsterdam', 'Europe/Amsterdam'), + (426, 'Europe/Andorra', 'Europe/Andorra'), + (427, 'Europe/Astrakhan', 'Europe/Astrakhan'), + (428, 'Europe/Athens', 'Europe/Athens'), + (429, 'Europe/Belfast', 'Europe/Belfast'), + (430, 'Europe/Belgrade', 'Europe/Belgrade'), + (431, 'Europe/Berlin', 'Europe/Berlin'), + (432, 'Europe/Bratislava', 'Europe/Bratislava'), + (433, 'Europe/Brussels', 'Europe/Brussels'), + (434, 'Europe/Bucharest', 'Europe/Bucharest'), + (435, 'Europe/Budapest', 'Europe/Budapest'), + (436, 'Europe/Busingen', 'Europe/Busingen'), + (437, 'Europe/Chisinau', 'Europe/Chisinau'), + (438, 'Europe/Copenhagen', 'Europe/Copenhagen'), + (439, 'Europe/Dublin', 'Europe/Dublin'), + (440, 'Europe/Gibraltar', 'Europe/Gibraltar'), + (441, 'Europe/Guernsey', 'Europe/Guernsey'), + (442, 'Europe/Helsinki', 'Europe/Helsinki'), + (443, 'Europe/Isle_of_Man', 'Europe/Isle_of_Man'), + (444, 'Europe/Istanbul', 'Europe/Istanbul'), + (445, 'Europe/Jersey', 'Europe/Jersey'), + (446, 'Europe/Kaliningrad', 'Europe/Kaliningrad'), + (447, 'Europe/Kiev', 'Europe/Kiev'), + (448, 'Europe/Kirov', 'Europe/Kirov'), + (449, 'Europe/Lisbon', 'Europe/Lisbon'), + (450, 'Europe/Ljubljana', 'Europe/Ljubljana'), + (451, 'Europe/London', 'Europe/London'), + (452, 'Europe/Luxembourg', 'Europe/Luxembourg'), + (453, 'Europe/Madrid', 'Europe/Madrid'), + (454, 'Europe/Malta', 'Europe/Malta'), + (455, 'Europe/Mariehamn', 'Europe/Mariehamn'), + (456, 'Europe/Minsk', 'Europe/Minsk'), + (457, 'Europe/Monaco', 'Europe/Monaco'), + (458, 'Europe/Moscow', 'Europe/Moscow'), + (459, 'Europe/Nicosia', 'Europe/Nicosia'), + (460, 'Europe/Oslo', 'Europe/Oslo'), + (461, 'Europe/Paris', 'Europe/Paris'), + (462, 'Europe/Podgorica', 'Europe/Podgorica'), + (463, 'Europe/Prague', 'Europe/Prague'), + (464, 'Europe/Riga', 'Europe/Riga'), + (465, 'Europe/Rome', 'Europe/Rome'), + (466, 'Europe/Samara', 'Europe/Samara'), + (467, 'Europe/San_Marino', 'Europe/San_Marino'), + (468, 'Europe/Sarajevo', 'Europe/Sarajevo'), + (469, 'Europe/Saratov', 'Europe/Saratov'), + (470, 'Europe/Simferopol', 'Europe/Simferopol'), + (471, 'Europe/Skopje', 'Europe/Skopje'), + (472, 'Europe/Sofia', 'Europe/Sofia'), + (473, 'Europe/Stockholm', 'Europe/Stockholm'), + (474, 'Europe/Tallinn', 'Europe/Tallinn'), + (475, 'Europe/Tirane', 'Europe/Tirane'), + (476, 'Europe/Tiraspol', 'Europe/Tiraspol'), + (477, 'Europe/Ulyanovsk', 'Europe/Ulyanovsk'), + (478, 'Europe/Uzhgorod', 'Europe/Uzhgorod'), + (479, 'Europe/Vaduz', 'Europe/Vaduz'), + (480, 'Europe/Vatican', 'Europe/Vatican'), + (481, 'Europe/Vienna', 'Europe/Vienna'), + (482, 'Europe/Vilnius', 'Europe/Vilnius'), + (483, 'Europe/Volgograd', 'Europe/Volgograd'), + (484, 'Europe/Warsaw', 'Europe/Warsaw'), + (485, 'Europe/Zagreb', 'Europe/Zagreb'), + (486, 'Europe/Zaporozhye', 'Europe/Zaporozhye'), + (487, 'Europe/Zurich', 'Europe/Zurich'), + (488, 'GB', 'GB'), + (489, 'GB-Eire', 'GB-Eire'), + (490, 'GMT', 'GMT'), + (491, 'GMT+0', 'GMT+0'), + (492, 'GMT-0', 'GMT-0'), + (493, 'GMT0', 'GMT0'), + (494, 'Greenwich', 'Greenwich'), + (495, 'HST', 'HST'), + (496, 'Hongkong', 'Hongkong'), + (497, 'Iceland', 'Iceland'), + (498, 'Indian/Antananarivo', 'Indian/Antananarivo'), + (499, 'Indian/Chagos', 'Indian/Chagos'), + (500, 'Indian/Christmas', 'Indian/Christmas'), + (501, 'Indian/Cocos', 'Indian/Cocos'), + (502, 'Indian/Comoro', 'Indian/Comoro'), + (503, 'Indian/Kerguelen', 'Indian/Kerguelen'), + (504, 'Indian/Mahe', 'Indian/Mahe'), + (505, 'Indian/Maldives', 'Indian/Maldives'), + (506, 'Indian/Mauritius', 'Indian/Mauritius'), + (507, 'Indian/Mayotte', 'Indian/Mayotte'), + (508, 'Indian/Reunion', 'Indian/Reunion'), + (509, 'Iran', 'Iran'), + (510, 'Israel', 'Israel'), + (511, 'Jamaica', 'Jamaica'), + (512, 'Japan', 'Japan'), + (513, 'Kwajalein', 'Kwajalein'), + (514, 'Libya', 'Libya'), + (515, 'MET', 'MET'), + (516, 'MST', 'MST'), + (517, 'MST7MDT', 'MST7MDT'), + (518, 'Mexico/BajaNorte', 'Mexico/BajaNorte'), + (519, 'Mexico/BajaSur', 'Mexico/BajaSur'), + (520, 'Mexico/General', 'Mexico/General'), + (521, 'NZ', 'NZ'), + (522, 'NZ-CHAT', 'NZ-CHAT'), + (523, 'Navajo', 'Navajo'), + (524, 'PRC', 'PRC'), + (525, 'PST8PDT', 'PST8PDT'), + (526, 'Pacific/Apia', 'Pacific/Apia'), + (527, 'Pacific/Auckland', 'Pacific/Auckland'), + (528, 'Pacific/Bougainville', 'Pacific/Bougainville'), + (529, 'Pacific/Chatham', 'Pacific/Chatham'), + (530, 'Pacific/Chuuk', 'Pacific/Chuuk'), + (531, 'Pacific/Easter', 'Pacific/Easter'), + (532, 'Pacific/Efate', 'Pacific/Efate'), + (533, 'Pacific/Enderbury', 'Pacific/Enderbury'), + (534, 'Pacific/Fakaofo', 'Pacific/Fakaofo'), + (535, 'Pacific/Fiji', 'Pacific/Fiji'), + (536, 'Pacific/Funafuti', 'Pacific/Funafuti'), + (537, 'Pacific/Galapagos', 'Pacific/Galapagos'), + (538, 'Pacific/Gambier', 'Pacific/Gambier'), + (539, 'Pacific/Guadalcanal', 'Pacific/Guadalcanal'), + (540, 'Pacific/Guam', 'Pacific/Guam'), + (541, 'Pacific/Honolulu', 'Pacific/Honolulu'), + (542, 'Pacific/Johnston', 'Pacific/Johnston'), + (543, 'Pacific/Kiritimati', 'Pacific/Kiritimati'), + (544, 'Pacific/Kosrae', 'Pacific/Kosrae'), + (545, 'Pacific/Kwajalein', 'Pacific/Kwajalein'), + (546, 'Pacific/Majuro', 'Pacific/Majuro'), + (547, 'Pacific/Marquesas', 'Pacific/Marquesas'), + (548, 'Pacific/Midway', 'Pacific/Midway'), + (549, 'Pacific/Nauru', 'Pacific/Nauru'), + (550, 'Pacific/Niue', 'Pacific/Niue'), + (551, 'Pacific/Norfolk', 'Pacific/Norfolk'), + (552, 'Pacific/Noumea', 'Pacific/Noumea'), + (553, 'Pacific/Pago_Pago', 'Pacific/Pago_Pago'), + (554, 'Pacific/Palau', 'Pacific/Palau'), + (555, 'Pacific/Pitcairn', 'Pacific/Pitcairn'), + (556, 'Pacific/Pohnpei', 'Pacific/Pohnpei'), + (557, 'Pacific/Ponape', 'Pacific/Ponape'), + (558, 'Pacific/Port_Moresby', 'Pacific/Port_Moresby'), + (559, 'Pacific/Rarotonga', 'Pacific/Rarotonga'), + (560, 'Pacific/Saipan', 'Pacific/Saipan'), + (561, 'Pacific/Samoa', 'Pacific/Samoa'), + (562, 'Pacific/Tahiti', 'Pacific/Tahiti'), + (563, 'Pacific/Tarawa', 'Pacific/Tarawa'), + (564, 'Pacific/Tongatapu', 'Pacific/Tongatapu'), + (565, 'Pacific/Truk', 'Pacific/Truk'), + (566, 'Pacific/Wake', 'Pacific/Wake'), + (567, 'Pacific/Wallis', 'Pacific/Wallis'), + (568, 'Pacific/Yap', 'Pacific/Yap'), + (569, 'Poland', 'Poland'), + (570, 'Portugal', 'Portugal'), + (571, 'ROC', 'ROC'), + (572, 'ROK', 'ROK'), + (573, 'Singapore', 'Singapore'), + (574, 'Turkey', 'Turkey'), + (575, 'UCT', 'UCT'), + (576, 'US/Alaska', 'US/Alaska'), + (577, 'US/Aleutian', 'US/Aleutian'), + (578, 'US/Arizona', 'US/Arizona'), + (579, 'US/Central', 'US/Central'), + (580, 'US/East-Indiana', 'US/East-Indiana'), + (581, 'US/Eastern', 'US/Eastern'), + (582, 'US/Hawaii', 'US/Hawaii'), + (583, 'US/Indiana-Starke', 'US/Indiana-Starke'), + (584, 'US/Michigan', 'US/Michigan'), + (585, 'US/Mountain', 'US/Mountain'), + (586, 'US/Pacific', 'US/Pacific'), + (587, 'US/Samoa', 'US/Samoa'), + (588, 'UTC', 'UTC'), + (589, 'Universal', 'Universal'), + (590, 'W-SU', 'W-SU'), + (591, 'WET', 'WET'), + (592, 'Zulu', 'Zulu'); + + +-- Timeseries: Additional Metadata +-- =============================== + +-- Absorption Cross Section + +CREATE TABLE IF NOT EXISTS CS_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT cs_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO CS_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Hearn1961', 'Hearn 1961'), + (1, 'CCQM.O3.2019', 'CCQM values of 2019 for O3'); + +-- Sampling Type (KS: Kind of Sampling) + +CREATE TABLE IF NOT EXISTS KS_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT ks_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO KS_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Continuous', 'continuous'), + (1, 'Filter', 'filter'), + (2, 'Flask', 'flask'); + +-- Calibration Type + +CREATE TABLE IF NOT EXISTS CT_vocabulary ( + enum_val INT NOT NULL, + enum_str character varying(128) NOT NULL, + enum_display_str character varying(128) NOT NULL, + PRIMARY KEY(enum_val, enum_str), + CONSTRAINT ct_enum_val_unique UNIQUE (enum_val) +); + +INSERT INTO CT_vocabulary (enum_val, enum_str, enum_display_str) VALUES + (0, 'Automatic', 'automatic'), + (1, 'Manual', 'manual'); + diff --git a/extension/toar_controlled_vocabulary/toar_controlled_vocabulary.control b/extension/toar_controlled_vocabulary/toar_controlled_vocabulary.control index cd4c997aa12f44143606bb9e5a535c7424847641..d6bf2df20f533075c9e69cdacd05449e6e2807c4 100644 --- a/extension/toar_controlled_vocabulary/toar_controlled_vocabulary.control +++ b/extension/toar_controlled_vocabulary/toar_controlled_vocabulary.control @@ -1,5 +1,5 @@ # toar_controlled_vocabulary extension comment = 'TOAR controlled vocabulary' -default_version = '0.7.6' -module_pathname = '$libdir/toar_controlled_vocabulary-0.7.6' +default_version = '0.7.7' +module_pathname = '$libdir/toar_controlled_vocabulary-0.7.7' relocatable = false diff --git a/production_tests.sh b/production_tests.sh index f4a43649eed08dff7a2daca244e0832b7e8aaf3d..02222522952bd6d7952a286d3e271d94c2d1f8e8 100755 --- a/production_tests.sh +++ b/production_tests.sh @@ -50,3 +50,5 @@ curl -X POST -H 'Content-Type: multipart/form-data; charset=utf-8; boundary=__X_ curl -X PATCH -H 'Content-Type: multipart/form-data; charset=utf-8; boundary=__X_PAW_BOUNDARY__' -F "file=@o3_CO002_2012_2017_v1-0.dat" "http://127.0.0.1:8000/data/timeseries/?description=fixed%20formatting%20errors%20on%20data&version=000001.000001.00000000000000" # add one record (including changelog entry) curl -X POST -H "Content-Type:application/json" "http://127.0.0.1:8000/data/timeseries/record/?series_id=96&datetime=2021-08-23%2015:00:00&value=67.3&flag=OK&version=000001.000001.00000000000000" +# register list of timeseries (ids) contributing to a service request +curl -X POST -H "Content-Type:application/json" -d '''[1,2,3]''' "http://127.0.0.1:8000/timeseries/register_timeseries_list_of_contributors/5f0df73a-bd0f-48b9-bb17-d5cd36f89598" diff --git a/static/db_statistics.json b/static/db_statistics.json new file mode 100644 index 0000000000000000000000000000000000000000..32f95061ca19b895435ca3b1c4f9904d84914812 --- /dev/null +++ b/static/db_statistics.json @@ -0,0 +1,7 @@ +{ + "users": 0, + "stations": 23979, + "time-series": 432880, + "data records": 65637800808 +} + diff --git a/tests/fixtures/data/data.json b/tests/fixtures/data/data.json index 5b2800048b128143d3c2833ffef6575ea3c16ba5..db41d6d70ae8f2dcd0400370a376665b22a9b5cb 100644 --- a/tests/fixtures/data/data.json +++ b/tests/fixtures/data/data.json @@ -124,5 +124,47 @@ "flags":0, "timeseries_id":2, "version":"000001.000000.00000000000000" + }, + { + "datetime":"2014-12-16 23:00:00+00", + "value":13.734, + "flags":1, + "timeseries_id":2, + "version":"000000.000000.20141217235502" + }, + { + "datetime":"2014-12-17 00:00:00+00", + "value":7.848, + "flags":2, + "timeseries_id":2, + "version":"000000.000000.20141217235502" + }, + { + "datetime":"2014-12-17 01:00:00+00", + "value":15.696, + "flags":2, + "timeseries_id":2, + "version":"000000.000000.20141217235502" + }, + { + "datetime":"2014-12-17 02:00:00+00", + "value":11.772, + "flags":0, + "timeseries_id":2, + "version":"000000.000000.20141217235502" + }, + { + "datetime":"2014-12-17 03:00:00+00", + "value":13.734, + "flags":1, + "timeseries_id":2, + "version":"000000.000000.20141217235502" + }, + { + "datetime":"2014-12-17 04:00:00+00", + "value":19.62, + "flags":0, + "timeseries_id":2, + "version":"000000.000000.20141217235502" } ] diff --git a/tests/fixtures/stationmeta/stationmeta_core.json b/tests/fixtures/stationmeta/stationmeta_core.json index 6120ac45aee7e3ff24c6f880edb94254c64c7ded..c2d1be388c000e49babfd2d2f1ae95626da1c37b 100644 --- a/tests/fixtures/stationmeta/stationmeta_core.json +++ b/tests/fixtures/stationmeta/stationmeta_core.json @@ -8,7 +8,7 @@ "type": 0, "type_of_area": 0, "timezone": 310, - "additional_metadata": {} + "additional_metadata": {"dummy_info": "Here is some more information about the station"} }, { "codes": ["SDZ54421"], @@ -19,7 +19,7 @@ "type": 0, "type_of_area": 0, "timezone": 310, - "additional_metadata": {"dummy_info": "Here is some more information about the station" } + "additional_metadata": {"add_type": "nature reservation"} }, { "codes":["China_test8"], diff --git a/tests/fixtures/stationmeta/stationmeta_global.json b/tests/fixtures/stationmeta/stationmeta_global.json index d20512d6e3ef802603bbe69d49079c30dcdc8255..d14ba7af60f096a51c472466ef6ac585dbd13163 100644 --- a/tests/fixtures/stationmeta/stationmeta_global.json +++ b/tests/fixtures/stationmeta/stationmeta_global.json @@ -24,6 +24,7 @@ "mean_nox_emissions_10km_year2015":-999.0, "mean_nox_emissions_10km_year2000":-999.0, "toar1_category":0, + "toar2_category":1, "station_id":1}, {"mean_topography_srtm_alt_90m_year1994":-999.0, "mean_topography_srtm_alt_1km_year1994":-999.0, @@ -50,6 +51,7 @@ "mean_nox_emissions_10km_year2015":-999.0, "mean_nox_emissions_10km_year2000":-999.0, "toar1_category":0, + "toar2_category":2, "station_id":2}, {"mean_topography_srtm_alt_90m_year1994":-999.0, "mean_topography_srtm_alt_1km_year1994":-999.0, @@ -76,5 +78,6 @@ "mean_nox_emissions_10km_year2015":-999.0, "mean_nox_emissions_10km_year2000":-999.0, "toar1_category":0, + "toar2_category":2, "station_id":3} ] diff --git a/tests/fixtures/timeseries/timeseries.json b/tests/fixtures/timeseries/timeseries.json index fa3d1644fda83cecb8734eb7ebf0a113fd88660d..47e89337793c5427ee7e9d1ae27003434910d324 100644 --- a/tests/fixtures/timeseries/timeseries.json +++ b/tests/fixtures/timeseries/timeseries.json @@ -11,7 +11,8 @@ "data_origin": 0, "data_origin_type": 0, "sampling_height": 7, - "additional_metadata": {} + "additional_metadata": {"original_units": "ppb"}, + "programme_id": 0 }, { "station_id": 3, @@ -32,6 +33,7 @@ "Data level": "2", "Frameworks": "GAW-WDCRG NOAA-ESRL", "Station code": "XXX", - "Station name": "Secret" } } + "Station name": "Secret" } }, + "programme_id": 0 } ] diff --git a/tests/fixtures/timeseries/timeseries_contributors.json b/tests/fixtures/timeseries/timeseries_contributors.json new file mode 100644 index 0000000000000000000000000000000000000000..d010d1015498d3bae7560bd8931f81910c0f811d --- /dev/null +++ b/tests/fixtures/timeseries/timeseries_contributors.json @@ -0,0 +1,6 @@ +[ + { + "request_id": "7f0df73a-bd0f-48b9-bb17-d5cd36f89598", + "timeseries_ids": [1, 2] + } +] diff --git a/tests/fixtures/timeseries/timeseries_roles.json b/tests/fixtures/timeseries/timeseries_roles.json index 3d7dc6a964c216c371d2fbf02806184ba25de700..dbf7fae69e68d058db7febc817663d7bf936f185 100644 --- a/tests/fixtures/timeseries/timeseries_roles.json +++ b/tests/fixtures/timeseries/timeseries_roles.json @@ -10,5 +10,11 @@ "role": 5, "status": 0, "contact_id": 4 + }, + { + "id": 3, + "role": 1, + "status": 0, + "contact_id": 3 } ] diff --git a/tests/fixtures/timeseries/timeseries_timeseries_roles.json b/tests/fixtures/timeseries/timeseries_timeseries_roles.json index c9b3fbc9c8c845e1bcbdfcc6362f14404441afdc..562603fe5fb62c67bb83b5eb893678a7c93b430c 100644 --- a/tests/fixtures/timeseries/timeseries_timeseries_roles.json +++ b/tests/fixtures/timeseries/timeseries_timeseries_roles.json @@ -4,6 +4,11 @@ "timeseries_id": 1, "role_id": 2 }, + { + "_comment": "Every time series needs at least one role (time series without roles are not allowed!)", + "timeseries_id": 1, + "role_id": 3 + }, { "_comment": "Every time series needs at least one role (time series without roles are not allowed!)", "timeseries_id": 2, diff --git a/tests/fixtures/toardb_pytest.psql b/tests/fixtures/toardb_pytest.psql index 372960d688a8bb1177149bff228bc55cddf13b05..02fddc8e46d388dba6626eded813485a3f8b7e85 100644 --- a/tests/fixtures/toardb_pytest.psql +++ b/tests/fixtures/toardb_pytest.psql @@ -25,6 +25,16 @@ CREATE SCHEMA IF NOT EXISTS toar_convoc; ALTER SCHEMA toar_convoc OWNER TO postgres; +-- +-- Name: services; Type: SCHEMA; Schema: -; Owner: postgres +-- + +CREATE SCHEMA IF NOT EXISTS services; + + +ALTER SCHEMA services OWNER TO postgres; + + -- -- Name: address_standardizer; Type: EXTENSION; Schema: -; Owner: - -- @@ -102,6 +112,18 @@ CREATE EXTENSION IF NOT EXISTS postgis_topology WITH SCHEMA topology; COMMENT ON EXTENSION postgis_topology IS 'PostGIS topology spatial types and functions'; +-- +-- List of contributors for request of special services +-- here: s1: analysis-service +-- + +CREATE TABLE IF NOT EXISTS services.s1_contributors ( + request_id character varying(36) NOT NULL, + timeseries_ids bigint[], + PRIMARY KEY(request_id) +); + + -- -- Name: toar_controlled_vocabulary; Type: EXTENSION -- faked; Schema: -; Owner: - -- @@ -2797,6 +2819,7 @@ CREATE TABLE IF NOT EXISTS public.stationmeta_global ( max_topography_srtm_relative_alt_5km_year1994 double precision DEFAULT '-999.0'::numeric NOT NULL, dominant_landcover_year2012 integer DEFAULT '-1'::integer NOT NULL, toar1_category integer DEFAULT '-1'::integer NOT NULL, + toar2_category integer DEFAULT '0'::integer NOT NULL, station_id integer NOT NULL, min_topography_srtm_relative_alt_5km_year1994 double precision DEFAULT '-999.0'::numeric NOT NULL, stddev_topography_srtm_relative_alt_5km_year1994 double precision DEFAULT '-999.0'::numeric NOT NULL, @@ -3896,6 +3919,14 @@ ALTER TABLE ONLY public.stationmeta_global ADD CONSTRAINT stationmeta_global_toar1_category_fk_tc_vocabulary_enum_val FOREIGN KEY (toar1_category) REFERENCES toar_convoc.tc_vocabulary(enum_val); +-- +-- Name: stationmeta_global stationmeta_global_toar2_category_fk_ta_vocabulary_enum_val; Type: FK CONSTRAINT; Schema: public; Owner: postgres +-- + +ALTER TABLE ONLY public.stationmeta_global + ADD CONSTRAINT stationmeta_global_toar2_category_fk_ta_vocabulary_enum_val FOREIGN KEY (toar2_category) REFERENCES toar_convoc.ta_vocabulary(enum_val); + + -- -- Name: stationmeta_roles stationmeta_roles_contact_id_fk_contacts_id; Type: FK CONSTRAINT; Schema: public; Owner: postgres -- @@ -4064,5 +4095,5 @@ ALTER TABLE ONLY public.timeseries ADD CONSTRAINT timeseries_variable_id_dd9603f5_fk_variables_id FOREIGN KEY (variable_id) REFERENCES public.variables(id) DEFERRABLE INITIALLY DEFERRED; ALTER DATABASE postgres - SET search_path=public,tiger,toar_convoc; + SET search_path=public,tiger,toar_convoc,services; diff --git a/tests/test_data.py b/tests/test_data.py index 2e8378883280cf5ace763fd731feff683b55a7ad..5dac34c8b27bcfc4f68775df837fbbbf2e7a7662 100644 --- a/tests/test_data.py +++ b/tests/test_data.py @@ -206,23 +206,24 @@ class TestApps: 'doi': '', 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, 'data_origin': 'instrument', 'data_origin_type': 'measurement', 'provider_version': 'N/A', - 'sampling_height': 7.0, 'additional_metadata': {}, + 'sampling_height': 7.0, 'additional_metadata': {'original_units': 'ppb'}, 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'coordinate_validation_status': 'not checked', 'country': 'China', 'state': 'Beijing Shi', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'roles': [], - 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': None, 'annotations': [], 'changelog': []}, + 'additional_metadata': {'add_type': 'nature reservation'}, + 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'changelog': []}, 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene', 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1', 'chemical_formula': 'C7H8', 'id': 7}, 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}, 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active', - 'contact': {'id': 4, 'person': None, - 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', + 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1', - 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}], - 'changelog': None, - 'citation': 'Umweltbundesamt: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'}, + 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, 'role': 'principal investigator', 'status': 'active', + 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de','id': 3, 'isprivate': False, + 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}} + ], + 'citation': 'Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'}, 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1}, {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1}, {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'version': '1.0', 'timeseries_id': 1}, @@ -273,7 +274,7 @@ class TestApps: 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, - 'additional_metadata': {}, + 'additional_metadata': {'original_units': 'ppb'}, 'doi': '', 'coverage': -1.0, 'station': {'id': 2, @@ -288,14 +289,10 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some ' - 'more information about the station'}, - 'roles': [], - 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None, 'changelog': []}, 'variable': {'name': 'toluene', 'longname': 'toluene', @@ -313,7 +310,6 @@ class TestApps: 'role': 'resource provider', 'status': 'active', 'contact': {'id': 4, - 'person': None, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', @@ -323,9 +319,18 @@ class TestApps: 'street_address': 'Wörlitzer Platz 1', 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', - 'contact_url': 'mailto:immission@uba.de'}}}], - 'changelog': None, - 'citation': 'Umweltbundesamt: time series of toluene at ' + 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}}], + 'citation': 'Sabine Schröder: time series of toluene at ' 'Shangdianzi, accessed from the TOAR database on ' '2023-07-28 12:00:00', 'license': 'This data is published under a Creative Commons ' @@ -363,7 +368,7 @@ class TestApps: 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, - 'additional_metadata': {}, + 'additional_metadata': {'original_units': 'ppb'}, 'doi': '', 'coverage': -1.0, 'station': {'id': 2, @@ -378,14 +383,10 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some ' - 'more information about the station'}, - 'roles': [], - 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None, 'changelog': []}, 'variable': {'name': 'toluene', 'longname': 'toluene', @@ -403,7 +404,6 @@ class TestApps: 'role': 'resource provider', 'status': 'active', 'contact': {'id': 4, - 'person': None, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', @@ -413,9 +413,17 @@ class TestApps: 'street_address': 'Wörlitzer Platz 1', 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', - 'contact_url': 'mailto:immission@uba.de'}}}], - 'changelog': None, - 'citation': 'Umweltbundesamt: time series of toluene at ' + 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}}], + 'citation': 'Sabine Schröder: time series of toluene at ' 'Shangdianzi, accessed from the TOAR database on ' '2023-07-28 12:00:00', 'license': 'This data is published under a Creative Commons ' @@ -448,7 +456,9 @@ class TestApps: '# "data_origin_type": "measurement",\n', '# "provider_version": "N/A",\n', '# "sampling_height": 7.0,\n', - '# "additional_metadata": {},\n', + '# "additional_metadata": {\n', + '# "original_units": "ppb"\n', + '# },\n', '# "data_license_accepted": null,\n', '# "dataset_approved_by_provider": null,\n', '# "doi": "",\n', @@ -471,10 +481,10 @@ class TestApps: '# "type_of_area": "unknown",\n', '# "timezone": "Asia/Shanghai",\n', '# "additional_metadata": {\n', - '# "dummy_info": "Here is some more information about the station"\n', + '# "add_type": "nature reservation"\n', '# },\n', - '# "roles": [],\n', - '# "annotations": [],\n', + '# "roles": null,\n', + '# "annotations": null,\n', '# "aux_images": [],\n', '# "aux_docs": [],\n', '# "aux_urls": [],\n', @@ -518,11 +528,28 @@ class TestApps: '# "contact_url": "mailto:immission@uba.de"\n', '# }\n', '# }\n', + '# },\n', + '# {\n', + '# "id": 3,\n', + '# "role": "principal investigator",\n', + '# "status": "active",\n', + '# "contact": {\n', + '# "id": 3,\n', + '# "person": {\n', + '# "id": 3,\n', + '# "name": "Sabine Schröder",\n', + '# "email": "s.schroeder@fz-juelich.de",\n', + '# "phone": "+49-2461-61-6397",\n', + '# "orcid": "0000-0002-0309-8010",\n', + '# "isprivate": false\n', + '# },\n', + '# "organisation": null\n', + '# }\n', '# }\n', '# ],\n', '# "annotations": null,\n', '# "changelog": null,\n', - '# "citation": "Umweltbundesamt: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n', + '# "citation": "Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00",\n', '# "attribution": null,\n', '# "license": "This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/"\n', '#}\n', @@ -639,21 +666,23 @@ class TestApps: 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/', 'doi': '', 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, 'data_origin': 'instrument', - 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, 'additional_metadata': {}, + 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, 'additional_metadata': {'original_units': 'ppb'}, 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'coordinate_validation_status': 'not checked', 'country': 'China', 'state': 'Beijing Shi', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': None, 'annotations': [], 'roles': [], 'changelog': []}, + 'additional_metadata': {'add_type': 'nature reservation'}, + 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'changelog': []}, 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene', 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1', 'chemical_formula': 'C7H8', 'id': 7}, 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}, 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active', - 'contact': {'id': 4, 'person': None, - 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', + 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1', - 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}], - 'changelog': None, 'citation': 'Umweltbundesamt: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'}, + 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, 'role': 'principal investigator', 'status': 'active', + 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de','id': 3, 'isprivate': False, + 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}}], + 'citation': 'Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database on 2023-07-28 12:00:00'}, 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'}, {'datetime': '2012-12-16T22:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'}, {'datetime': '2012-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'}, @@ -715,12 +744,9 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None, 'changelog': []}, 'variable': {'name': 'o3', 'longname': 'ozone', @@ -738,7 +764,6 @@ class TestApps: 'role': 'resource provider', 'status': 'active', 'contact': {'id': 5, - 'person': None, 'organisation': {'id': 2, 'name': 'FZJ', 'longname': 'Forschungszentrum Jülich', @@ -749,7 +774,6 @@ class TestApps: 'country': 'Germany', 'homepage': 'https://www.fz-juelich.de', 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}], - 'changelog': None, 'citation': 'Forschungszentrum Jülich: time series of o3 at Test_China, accessed from the TOAR database on 2023-07-28 12:00:00', 'attribution': 'Test-Attributions to be announced', 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/'}, @@ -795,12 +819,9 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None, 'changelog': []}, 'variable': {'name': 'o3', 'longname': 'ozone', @@ -818,7 +839,6 @@ class TestApps: 'role': 'resource provider', 'status': 'active', 'contact': {'id': 5, - 'person': None, 'organisation': {'id': 2, 'name': 'FZJ', 'longname': 'Forschungszentrum Jülich', @@ -829,7 +849,6 @@ class TestApps: 'country': 'Germany', 'homepage': 'https://www.fz-juelich.de', 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}], - 'changelog': None, 'citation': 'Forschungszentrum Jülich: time series of o3 at Test_China, accessed from the TOAR database on 2023-07-28 12:00:00', 'attribution': 'Test-Attributions to be announced', 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/'}, @@ -881,12 +900,9 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None, 'changelog': []}, 'variable': {'name': 'o3', 'longname': 'ozone', @@ -904,7 +920,6 @@ class TestApps: 'role': 'resource provider', 'status': 'active', 'contact': {'id': 5, - 'person': None, 'organisation': {'id': 2, 'name': 'FZJ', @@ -916,7 +931,6 @@ class TestApps: 'country': 'Germany', 'homepage': 'https://www.fz-juelich.de', 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}], - 'changelog': None, 'citation': 'Forschungszentrum Jülich: time series of o3 at ' 'Test_China, accessed from the TOAR database on ' '2023-07-28 12:00:00', @@ -942,7 +956,13 @@ class TestApps: {'datetime': '2013-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'questionable validated flagged', 'timeseries_id': 2, 'version': '1.0'}, {'datetime': '2013-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'questionable validated unconfirmed', 'timeseries_id': 2, 'version': '1.0'}, {'datetime': '2013-12-17T05:00:00+00:00', 'value': 15.696, 'flags': 'questionable validated flagged', 'timeseries_id': 2, 'version': '1.0'}, - {'datetime': '2013-12-17T06:00:00+00:00', 'value': 5.886, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'}] + {'datetime': '2013-12-17T06:00:00+00:00', 'value': 5.886, 'flags': 'questionable validated confirmed', 'timeseries_id': 2, 'version': '1.0'}, + {'datetime': '2014-12-16T23:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '0.0.20141217235502'}, + {'datetime': '2014-12-17T00:00:00+00:00', 'value': 7.848, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '0.0.20141217235502'}, + {'datetime': '2014-12-17T01:00:00+00:00', 'value': 15.696, 'flags': 'OK validated modified', 'timeseries_id': 2, 'version': '0.0.20141217235502'}, + {'datetime': '2014-12-17T02:00:00+00:00', 'value': 11.772, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '0.0.20141217235502'}, + {'datetime': '2014-12-17T03:00:00+00:00', 'value': 13.734, 'flags': 'OK validated QC passed', 'timeseries_id': 2, 'version': '0.0.20141217235502'}, + {'datetime': '2014-12-17T04:00:00+00:00', 'value': 19.62, 'flags': 'OK validated verified', 'timeseries_id': 2, 'version': '0.0.20141217235502'}] } assert response.json() == expected_response @@ -1028,7 +1048,7 @@ class TestApps: 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, - 'additional_metadata': {}, + 'additional_metadata': {'original_units': 'ppb'}, 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi', @@ -1039,13 +1059,10 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'roles': [], - 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None, 'changelog': [] }, 'variable': {'name': 'toluene', @@ -1063,7 +1080,7 @@ class TestApps: 'description': '' }, 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active', - 'contact': {'id': 4, 'person': None, + 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', @@ -1076,6 +1093,17 @@ class TestApps: 'contact_url': 'mailto:immission@uba.de', } } + }, + {'id': 3, 'role': 'principal investigator', 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397' + } + } }], 'changelog': [{'datetime': '', 'description': 'Sabine Schröder (s.schroeder@fz-juelich.de): test patch bulk data', @@ -1088,7 +1116,7 @@ class TestApps: 'period_end': '2012-12-17T04:00:00+00:00', 'version': '000002.000000.00000000000000' }], - 'citation': 'Umweltbundesamt: time series of toluene at Shangdianzi, accessed from the TOAR database', + 'citation': 'Sabine Schröder: time series of toluene at Shangdianzi, accessed from the TOAR database', 'license': 'This data is published under a Creative Commons Attribution 4.0 International (CC BY 4.0). https://creativecommons.org/licenses/by/4.0/', 'doi': ''}, 'data': [{'datetime': '2012-12-16T21:00:00+00:00', 'value': 21.581, 'flags': 'OK validated verified', 'timeseries_id': 1, 'version': '1.0'}, diff --git a/tests/test_search.py b/tests/test_search.py index 5117b491e5280d35c8cd0f0cfe973bb3e2424130..34061821483cdea4dffaeddc96222c719e5ee02d 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -187,7 +187,7 @@ class TestApps: 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, - 'additional_metadata': {}, + 'additional_metadata': {"original_units": "ppb"}, 'doi': '', 'coverage': -1.0, 'station': {'id': 2, @@ -200,9 +200,7 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'roles': [], - 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -230,7 +228,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-07-15T19:27:09.463245+00:00', 'description': 'station created', 'old_value': '', @@ -263,7 +262,17 @@ class TestApps: 'street_address': 'Wörlitzer Platz 1', 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', - 'contact_url': 'mailto:immission@uba.de'}}}]}, + 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}}]}, {'id': 2, 'label': 'CMA', 'order': 1, @@ -297,8 +306,6 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -326,7 +333,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -402,8 +410,8 @@ class TestApps: 'coordinate_validation_status': 'not checked', 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', - 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], + 'timezone': 'Asia/Shanghai', + 'additional_metadata': {}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -429,7 +437,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -473,7 +482,6 @@ class TestApps: 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -499,7 +507,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -559,7 +568,6 @@ class TestApps: 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -585,7 +593,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -629,7 +638,6 @@ class TestApps: 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -655,7 +663,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -680,9 +689,14 @@ class TestApps: response = client.get("/search/?fields=id,role,order,additional_metadata") expected_status_code = 200 assert response.status_code == expected_status_code - expected_resp = [{'id': 1, + expected_resp = [{'additional_metadata': {'original_units': 'ppb'}, + 'id': 1, + 'order': 1, + 'roles': {'contact_id': 3, 'role': 'principal investigator', 'status': 'active'} + }, + {'id': 1, 'order': 1, - 'additional_metadata': {}, + 'additional_metadata': {"original_units": "ppb"}, 'roles': {'role': 'resource provider', 'status': 'active', 'contact_id': 4} }, {'id': 2, @@ -750,7 +764,6 @@ class TestApps: 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -776,7 +789,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -820,7 +834,6 @@ class TestApps: 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -846,7 +859,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -890,7 +904,6 @@ class TestApps: 'country': 'China', 'state': 'Shandong Sheng', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -916,7 +929,79 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, + 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', + 'description': 'station created', + 'old_value': '', + 'new_value': '', + 'station_id': 3, + 'author_id': 1, + 'type_of_change': 'created' + }]}, + 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}}] + assert response.json() == expected_resp + + + def test_search_with_additional_metadata(self, client, db): + response = client.get("/search/?additional_metadata->'absorption_cross_section'=Hearn1961") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = [{'id': 2, 'label': 'CMA', 'order': 1, + 'sampling_frequency': 'hourly', 'aggregation': 'mean', 'data_origin_type': 'measurement', + 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, + 'data_origin': 'instrument', 'sampling_height': 7.0, + 'provider_version': 'N/A', + 'doi': '', + 'additional_metadata': {'absorption_cross_section': 'Hearn 1961', + 'measurement_method': 'uv_abs', + 'original_units': {'since_19740101000000': 'nmol/mol'}, + 'ebas_metadata_19740101000000_29y': {'Submitter': 'Unknown, Lady, lady.unknown@unknown.com, some long division name, SHORT, , 111 Streetname, , zipcode, Boulder, CO, USA', + 'Data level': '2', + 'Frameworks': 'GAW-WDCRG NOAA-ESRL', + 'Station code': 'XXX', + 'Station name': 'Secret' } }, + 'roles': [{'id': 1, 'role': 'resource provider', 'status': 'active', + 'contact': {'id': 5, 'organisation': {'id': 2, 'name': 'FZJ', 'longname': 'Forschungszentrum Jülich', + 'kind': 'research', 'city': 'Jülich', 'postcode': '52425', 'street_address': 'Wilhelm-Johnen-Straße', + 'country': 'Germany', 'homepage': 'https://www.fz-juelich.de', 'contact_url': 'mailto:toar-data@fz-juelich.de'}}}], + 'variable': {'name': 'o3', 'longname': 'ozone', 'displayname': 'Ozone', + 'cf_standardname': 'mole_fraction_of_ozone_in_air', 'units': 'nmol mol-1', + 'chemical_formula': 'O3', 'id': 5}, + 'station': {'id': 3, 'codes': ['China_test8'], 'name': 'Test_China', + 'coordinates': {'alt': 1534.0, 'lat': 36.256, 'lng': 117.106}, + 'coordinate_validation_status': 'not checked', + 'country': 'China', 'state': 'Shandong Sheng', + 'type': 'unknown', 'type_of_area': 'unknown', + 'timezone': 'Asia/Shanghai', + 'additional_metadata': {}, + 'aux_images': [], 'aux_docs': [], 'aux_urls': [], + 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', + 'distance_to_major_road_year2020': -999.0, + 'dominant_ecoregion_year2017': '-1 (undefined)', + 'dominant_landcover_year2012': '10 (Cropland, rainfed)', + 'ecoregion_description_25km_year2017': '', + 'htap_region_tier1_year2010': '10 (SAF Sub Saharan/sub Sahel Africa)', + 'landcover_description_25km_year2012': '', + 'max_stable_nightlights_25km_year1992': -999.0, + 'max_stable_nightlights_25km_year2013': -999.0, + 'max_population_density_25km_year1990': -1.0, + 'max_population_density_25km_year2015': -1.0, + 'max_topography_srtm_relative_alt_5km_year1994': -999.0, + 'mean_stable_nightlights_1km_year2013': -999.0, + 'mean_stable_nightlights_5km_year2013': -999.0, + 'mean_nox_emissions_10km_year2000': -999.0, + 'mean_nox_emissions_10km_year2015': -999.0, + 'mean_population_density_250m_year1990': -1.0, + 'mean_population_density_250m_year2015': -1.0, + 'mean_population_density_5km_year1990': -1.0, + 'mean_population_density_5km_year2015': -1.0, + 'mean_topography_srtm_alt_1km_year1994': -999.0, + 'mean_topography_srtm_alt_90m_year1994': -999.0, + 'min_topography_srtm_relative_alt_5km_year1994': -999.0, + 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -927,3 +1012,225 @@ class TestApps: }]}, 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}}] assert response.json() == expected_resp + + + def test_search_with_additional_metadata2(self, client, db): + response = client.get("/search/?additional_metadata->'absorption_cross_section'=Hearn1961"+ + "&additional_metadata->'sampling_type'=Continuous"+ + "&additional_metadata->'calibration_type'=Automatic") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = [] + assert response.json() == expected_response + + + def test_search_with_additional_metadata3(self, client, db): + response = client.get("/search/?additional_metadata->'original_units'=ppb") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = [{'id': 1, + 'label': 'CMA', + 'order': 1, + 'sampling_frequency': 'hourly', + 'aggregation': 'mean', + 'data_start_date': '2003-09-07T15:30:00+00:00', + 'data_end_date': '2016-12-31T14:30:00+00:00', + 'data_origin': 'instrument', + 'data_origin_type': 'measurement', + 'provider_version': 'N/A', + 'sampling_height': 7.0, + 'additional_metadata': {"original_units": "ppb"}, + 'doi': '', + 'coverage': -1.0, + 'station': {'id': 2, + 'codes': ['SDZ54421'], + 'name': 'Shangdianzi', + 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, + 'coordinate_validation_status': 'not checked', + 'country': 'China', + 'state': 'Beijing Shi', + 'type': 'unknown', + 'type_of_area': 'unknown', + 'timezone': 'Asia/Shanghai', + 'additional_metadata': {'add_type': 'nature reservation'}, + 'aux_images': [], + 'aux_docs': [], + 'aux_urls': [], + 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, + 'mean_topography_srtm_alt_1km_year1994': -999.0, + 'max_topography_srtm_relative_alt_5km_year1994': -999.0, + 'min_topography_srtm_relative_alt_5km_year1994': -999.0, + 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, + 'climatic_zone_year2016': '6 (warm temperate dry)', + 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)', + 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)', + 'landcover_description_25km_year2012': '', + 'dominant_ecoregion_year2017': '-1 (undefined)', + 'ecoregion_description_25km_year2017': '', + 'distance_to_major_road_year2020': -999.0, + 'mean_stable_nightlights_1km_year2013': -999.0, + 'mean_stable_nightlights_5km_year2013': -999.0, + 'max_stable_nightlights_25km_year2013': -999.0, + 'max_stable_nightlights_25km_year1992': -999.0, + 'mean_population_density_250m_year2015': -1.0, + 'mean_population_density_5km_year2015': -1.0, + 'max_population_density_25km_year2015': -1.0, + 'mean_population_density_250m_year1990': -1.0, + 'mean_population_density_5km_year1990': -1.0, + 'max_population_density_25km_year1990': -1.0, + 'mean_nox_emissions_10km_year2015': -999.0, + 'mean_nox_emissions_10km_year2000': -999.0, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, + 'changelog': [{'datetime': '2023-07-15T19:27:09.463245+00:00', 'description': 'station created', 'old_value': '', 'new_value': '', 'station_id': 2, 'author_id': 1, 'type_of_change': 'created'}]}, + 'variable': {'name': 'toluene', + 'longname': 'toluene', + 'displayname': 'Toluene', + 'cf_standardname': 'mole_fraction_of_toluene_in_air', + 'units': 'nmol mol-1', + 'chemical_formula': 'C7H8', + 'id': 7}, + 'programme': {'id': 0, + 'name': '', + 'longname': '', + 'homepage': '', + 'description': ''}, + 'roles': [{'id': 2, + 'role': 'resource provider', + 'status': 'active', + 'contact': {'id': 4, + 'organisation': {'id': 1, + 'name': 'UBA', + 'longname': 'Umweltbundesamt', + 'kind': 'government', + 'city': 'Dessau-Roßlau', + 'postcode': '06844', + 'street_address': 'Wörlitzer Platz 1', + 'country': 'Germany', + 'homepage': 'https://www.umweltbundesamt.de', + 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397' + } + } + }] + }] + assert response.json() == expected_response + + + def test_search_with_additional_metadata_unknown(self, client, db): + response = client.get("/search/?additional_metadata->'not_yet_defined'=42") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = [] + assert response.json() == expected_response + + + def test_search_with_additional_metadata_station(self, client, db): + response = client.get("/search/?station_additional_metadata->'add_type'=nature reservation") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = [{'id': 1, + 'label': 'CMA', + 'order': 1, + 'sampling_frequency': 'hourly', + 'aggregation': 'mean', + 'data_start_date': '2003-09-07T15:30:00+00:00', + 'data_end_date': '2016-12-31T14:30:00+00:00', + 'data_origin': 'instrument', + 'data_origin_type': 'measurement', + 'provider_version': 'N/A', + 'sampling_height': 7.0, + 'additional_metadata': {"original_units": "ppb"}, + 'doi': '', + 'coverage': -1.0, + 'station': {'id': 2, + 'codes': ['SDZ54421'], + 'name': 'Shangdianzi', + 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, + 'coordinate_validation_status': 'not checked', + 'country': 'China', + 'state': 'Beijing Shi', + 'type': 'unknown', + 'type_of_area': 'unknown', + 'timezone': 'Asia/Shanghai', + 'additional_metadata': {'add_type': 'nature reservation'}, + 'aux_images': [], + 'aux_docs': [], + 'aux_urls': [], + 'globalmeta': {'mean_topography_srtm_alt_90m_year1994': -999.0, + 'mean_topography_srtm_alt_1km_year1994': -999.0, + 'max_topography_srtm_relative_alt_5km_year1994': -999.0, + 'min_topography_srtm_relative_alt_5km_year1994': -999.0, + 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, + 'climatic_zone_year2016': '6 (warm temperate dry)', + 'htap_region_tier1_year2010': '11 (MDE Middle East: S. Arabia, Oman, etc, Iran, Iraq)', + 'dominant_landcover_year2012': '11 (Cropland, rainfed, herbaceous cover)', + 'landcover_description_25km_year2012': '', + 'dominant_ecoregion_year2017': '-1 (undefined)', + 'ecoregion_description_25km_year2017': '', + 'distance_to_major_road_year2020': -999.0, + 'mean_stable_nightlights_1km_year2013': -999.0, + 'mean_stable_nightlights_5km_year2013': -999.0, + 'max_stable_nightlights_25km_year2013': -999.0, + 'max_stable_nightlights_25km_year1992': -999.0, + 'mean_population_density_250m_year2015': -1.0, + 'mean_population_density_5km_year2015': -1.0, + 'max_population_density_25km_year2015': -1.0, + 'mean_population_density_250m_year1990': -1.0, + 'mean_population_density_5km_year1990': -1.0, + 'max_population_density_25km_year1990': -1.0, + 'mean_nox_emissions_10km_year2015': -999.0, + 'mean_nox_emissions_10km_year2000': -999.0, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, + 'changelog': [{'datetime': '2023-07-15T19:27:09.463245+00:00', 'description': 'station created', 'old_value': '', 'new_value': '', 'station_id': 2, 'author_id': 1, 'type_of_change': 'created'}]}, + 'variable': {'name': 'toluene', + 'longname': 'toluene', + 'displayname': 'Toluene', + 'cf_standardname': 'mole_fraction_of_toluene_in_air', + 'units': 'nmol mol-1', + 'chemical_formula': 'C7H8', + 'id': 7}, + 'programme': {'id': 0, + 'name': '', + 'longname': '', + 'homepage': '', + 'description': ''}, + 'roles': [{'id': 2, + 'role': 'resource provider', + 'status': 'active', + 'contact': {'id': 4, + 'organisation': {'id': 1, + 'name': 'UBA', + 'longname': 'Umweltbundesamt', + 'kind': 'government', + 'city': 'Dessau-Roßlau', + 'postcode': '06844', + 'street_address': 'Wörlitzer Platz 1', + 'country': 'Germany', + 'homepage': 'https://www.umweltbundesamt.de', + 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397' + } + } + }] + }] + assert response.json() == expected_response diff --git a/tests/test_stationmeta.py b/tests/test_stationmeta.py index e98d180984b52194de61c139f435a45bbf534e08..aa5ca570a40661f995b543a8b2966aa8851864f5 100644 --- a/tests/test_stationmeta.py +++ b/tests/test_stationmeta.py @@ -175,7 +175,7 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {}, + 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active', @@ -189,7 +189,6 @@ class TestApps: 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -242,7 +241,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'urban'}, 'changelog': [{'datetime': '2023-07-05T08:23:04.551645+00:00', 'description': 'station created', 'old_value': '', @@ -260,8 +260,7 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about ' - 'the station'}, + 'additional_metadata': {'add_type': 'nature reservation'}, 'roles': [{'id': 1, 'role': 'resource provider', 'status': 'active', @@ -275,7 +274,6 @@ class TestApps: 'country': 'Germany', 'homepage': 'https://www.fz-juelich.de', 'contact_url': 'mailto:toar-data@fz-juelich.de'}}], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -304,7 +302,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-07-15T19:27:09.463245+00:00', 'description': 'station created', 'old_value': '', @@ -323,8 +322,6 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -353,7 +350,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -375,7 +373,7 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], 'annotations': [], 'aux_images': [], 'aux_docs': [], + 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -401,7 +399,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': [{'datetime': '2023-08-15T21:16:20.596545+00:00', 'description': 'station created', 'old_value': '', @@ -442,7 +441,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified' + 'toar1_category': 'unclassified', + 'toar2_category': 'urban' } } assert response.json() == expected_resp @@ -492,7 +492,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified' + 'toar1_category': 'unclassified', + 'toar2_category': 'urban' } }, {'codes': ['SDZ54421'], @@ -520,7 +521,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified' + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban' } }, {'codes': ['China_test8'], @@ -548,7 +550,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified' + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban' } }] assert response.json() == expected_resp @@ -603,7 +606,8 @@ class TestApps: 'id': 2, 'role': 'resource provider', 'status': 'active' }], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], + 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, + 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, 'dominant_ecoregion_year2017': '-1 (undefined)', @@ -651,7 +655,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'urban'}, 'changelog': [{'datetime': '2023-07-05T08:23:04.551645+00:00', 'description': 'station created', 'old_value': '', @@ -895,6 +900,7 @@ class TestApps: json={"stationmeta": {"globalmeta": {"climatic_zone_year2016": "WarmTemperateMoist", "toar1_category": "RuralLowElevation", + "toar2_category": "Urban", "htap_region_tier1_year2010": "HTAPTier1PAN", "dominant_landcover_year2012": "TreeNeedleleavedEvergreenClosedToOpen", "landcover_description_25km_year2012": "TreeNeedleleavedEvergreenClosedToOpen: 100 %", @@ -919,7 +925,8 @@ class TestApps: "'landcover_description_25km_year2012': '', " "'dominant_ecoregion_year2017': 'Undefined', " "'ecoregion_description_25km_year2017': '', " - "'toar1_category': 'Unclassified'}" ) + "'toar1_category': 'Unclassified', " + "'toar2_category': 'Suburban'}" ) assert response_json['changelog'][1]['new_value'] == ( "{'climatic_zone_year2016': 'WarmTemperateMoist', " "'htap_region_tier1_year2010': 'HTAPTier1PAN', " @@ -927,33 +934,87 @@ class TestApps: "'landcover_description_25km_year2012': 'TreeNeedleleavedEvergreenClosedToOpen: 100 %', " "'dominant_ecoregion_year2017': 'Guianansavanna', " "'ecoregion_description_25km_year2017': 'Guianansavanna: 90 %, Miskitopineforests: 10 %'" - ", 'toar1_category': 'RuralLowElevation'}" ) + ", 'toar1_category': 'RuralLowElevation'" + ", 'toar2_category': 'Urban'}" ) assert response_json['changelog'][1]['author_id'] == 1 assert response_json['changelog'][1]['type_of_change'] == 'single value correction in metadata' + def test_patch_stationmeta_roles_and_annotations(self, client, db): + response = client.patch("/stationmeta/SDZ54421?description=adding annotation text", + json={"stationmeta": + {"roles": [{"role": "PointOfContact", "contact_id": 3, "status": "Active"}, + {"role": "Originator", "contact_id": 1, "status": "Active"}], + "annotations": [{"kind": "User", + "text": "some annotation text", + "date_added": "2025-02-10 17:00", + "approved": True, + "contributor_id":1}] + } + }, + headers={"email": "s.schroeder@fz-juelich.de"} + ) + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = {'message': 'patched stationmeta record for station_id 2', 'station_id': 2} + response_json = response.json() + assert response_json == expected_resp + response = client.get(f"/stationmeta/id/{response_json['station_id']}") + response_json = response.json() + assert response_json['annotations'] == [{'id': 1, 'kind': 'user comment', 'text': 'some annotation text', 'date_added': '2025-02-10T17:00:00+00:00', 'approved': True, 'contributor_id': 1}] + assert response_json['changelog'][1]['old_value'] == "{'roles': {{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 5},}" + assert response_json['changelog'][1]['new_value'] == ( + "{'roles': [{'role': 'PointOfContact', 'contact_id': 3, 'status': 'Active'}, " + "{'role': 'Originator', 'contact_id': 1, 'status': 'Active'}], " + "'annotations': [{'kind': 'User', 'text': 'some annotation text', 'date_added': '2025-02-10 17:00', 'approved': True, 'contributor_id': 1}]}" ) + assert response_json['changelog'][1]['author_id'] == 1 + assert response_json['changelog'][1]['type_of_change'] == 'comprehensive metadata revision' + + def test_delete_roles_from_stationmeta(self, client, db): response = client.patch("/stationmeta/delete_field/China11?field=roles", headers={"email": "s.schroeder@fz-juelich.de"}) expected_status_code = 200 assert response.status_code == expected_status_code - expected_resp = {'codes': ['China11'], + # Database defaults cannot be patched within pytest + patched_response = response.json() + patched_response["changelog"][-1]["datetime"] = "" + expected_resp = {'id': 1, + 'codes': ['China11'], 'name': 'Mount Tai', 'coordinates': {'lat': 36.256, 'lng': 117.106, 'alt': 1534.0}, - 'coordinate_validation_status': '0', - 'country': '48', + 'coordinate_validation_status': 'not checked', + 'country': 'China', 'state': 'Shandong Sheng', - 'type': '0', - 'type_of_area': '0', - 'timezone': '310', - 'additional_metadata': {}, - 'roles': [], - 'annotations': [], + 'type': 'unknown', + 'type_of_area': 'unknown', + 'timezone': 'Asia/Shanghai', + 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], - 'globalmeta': None} - assert response.json() == expected_resp + 'changelog': [ + { + 'author_id': 1, + 'datetime': '2023-07-05T08:23:04.551645+00:00', + 'description': 'station created', + 'new_value': '', + 'old_value': '', + 'station_id': 1, + 'type_of_change': 'created', + }, + { + 'author_id': 1, + 'datetime': '', + 'description': 'delete field roles', + 'new_value': "'roles': []", + 'old_value': "'roles': '[]'", + 'station_id': 1, + 'type_of_change': 'single value correction in metadata', + }, + ], + } + assert patched_response == expected_resp def test_delete_field_station_not_found(self, client, db): @@ -964,3 +1025,15 @@ class TestApps: expected_resp = {'detail': 'Station for deleting field not found.'} assert response.json() == expected_resp + + def test_test(self, client, db): + response = client.patch("/stationmeta/SDY54421?description=changing global metadata", + json={"stationmeta": + {"globalmeta": { "toar2_category": "Rural"}} + }, + headers={"email": "s.schroeder@fz-juelich.de"} + ) + expected_status_code = 404 + assert response.status_code == expected_status_code + expected_resp = {'detail': 'Station for patching not found.'} + assert response.json() == expected_resp diff --git a/tests/test_timeseries.py b/tests/test_timeseries.py index 72701316e1e2e2dd1909eb750c715ed3134747b1..5cb98235bda5e6f647830cf68aee266515fe51a2 100644 --- a/tests/test_timeseries.py +++ b/tests/test_timeseries.py @@ -4,7 +4,8 @@ import pytest import json from sqlalchemy import insert -from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table +from toardb.timeseries.models import Timeseries, timeseries_timeseries_roles_table, \ + s1_contributors_table from toardb.timeseries.models_programme import TimeseriesProgramme from toardb.timeseries.models_role import TimeseriesRole from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal @@ -68,7 +69,7 @@ class TestApps: fake_conn.commit() fake_cur.execute("ALTER SEQUENCE timeseries_annotations_id_seq RESTART WITH 1") fake_conn.commit() - fake_cur.execute("ALTER SEQUENCE timeseries_roles_id_seq RESTART WITH 3") + fake_cur.execute("ALTER SEQUENCE timeseries_roles_id_seq RESTART WITH 4") fake_conn.commit() fake_cur.execute("ALTER SEQUENCE timeseries_programmes_id_seq RESTART WITH 1") fake_conn.commit() @@ -165,6 +166,12 @@ class TestApps: for entry in metajson: db.execute(insert(timeseries_timeseries_roles_table).values(timeseries_id=entry["timeseries_id"], role_id=entry["role_id"])) db.execute("COMMIT") + infilename = "tests/fixtures/timeseries/timeseries_contributors.json" + with open(infilename) as f: + metajson=json.load(f) + for entry in metajson: + db.execute(insert(s1_contributors_table).values(request_id=entry["request_id"], timeseries_ids=entry["timeseries_ids"])) + db.execute("COMMIT") def test_get_timeseries(self, client, db): @@ -182,7 +189,7 @@ class TestApps: 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, - 'additional_metadata': {}, + 'additional_metadata': {'original_units': 'ppb'}, 'doi': '', 'coverage': -1.0, 'station': {'id': 2, @@ -195,11 +202,7 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more ' - 'information about the ' - 'station'}, - 'roles': [], - 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -231,7 +234,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': []}, 'variable': {'name': 'toluene', 'longname': 'toluene', @@ -258,7 +262,17 @@ class TestApps: 'street_address': 'Wörlitzer Platz 1', 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', - 'contact_url': 'mailto:immission@uba.de'}}}]}, + 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}}]}, {'id': 2, 'label': 'CMA', 'order': 1, @@ -307,8 +321,6 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -339,7 +351,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': []}, 'variable': {'name': 'o3', 'longname': 'ozone', @@ -379,11 +392,14 @@ class TestApps: 'sampling_frequency': 'hourly', 'aggregation': 'mean', 'data_origin_type': 'measurement', 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, 'data_origin': 'instrument', 'sampling_height': 7.0, - 'provider_version': 'N/A', 'doi': '', 'additional_metadata': {}, + 'provider_version': 'N/A', 'doi': '', 'additional_metadata': {'original_units': 'ppb'}, 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active', 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1', - 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}], + 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, 'role': 'principal investigator', 'status': 'active', + 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de', 'id': 3, 'isprivate': False, + 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}}], 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene', 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1', 'chemical_formula': 'C7H8', 'id': 7}, @@ -393,8 +409,7 @@ class TestApps: 'country': 'China', 'state': 'Beijing Shi', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'roles': [], 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, @@ -420,7 +435,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': []}, 'programme': {'id': 0, 'name': '', 'longname': '', 'homepage': '', 'description': ''}} assert response.json() == expected_resp @@ -603,19 +619,22 @@ class TestApps: 'id': 1, 'label': 'CMA', 'order': 1, 'sampling_frequency': 'hourly', 'aggregation': 'mean', 'data_start_date': '2003-09-07T15:30:00+00:00', 'data_end_date': '2016-12-31T14:30:00+00:00', 'coverage': -1.0, 'data_origin': 'instrument', 'data_origin_type': 'measurement', - 'provider_version': 'N/A', 'doi': '', 'sampling_height': 7.0, 'additional_metadata': {}, + 'provider_version': 'N/A', 'doi': '', 'sampling_height': 7.0, 'additional_metadata': {'original_units': 'ppb'}, 'roles': [{'id': 2, 'role': 'resource provider', 'status': 'active', 'contact': {'id': 4, 'organisation': {'id': 1, 'name': 'UBA', 'longname': 'Umweltbundesamt', 'kind': 'government', 'city': 'Dessau-Roßlau', 'postcode': '06844', 'street_address': 'Wörlitzer Platz 1', - 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}], + 'country': 'Germany', 'homepage': 'https://www.umweltbundesamt.de', 'contact_url': 'mailto:immission@uba.de'}}}, + {'id': 3, 'role': 'principal investigator', 'status': 'active', + 'contact': {'id': 3, 'person': {'email': 's.schroeder@fz-juelich.de','id': 3, 'isprivate': False, + 'name': 'Sabine Schröder', 'orcid': '0000-0002-0309-8010', 'phone': '+49-2461-61-6397'}}} + ], 'station': {'id': 2, 'codes': ['SDZ54421'], 'name': 'Shangdianzi', 'coordinates': {'lat': 40.65, 'lng': 117.106, 'alt': 293.9}, 'coordinate_validation_status': 'not checked', 'country': 'China', 'state': 'Beijing Shi', 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], + 'additional_metadata': {'add_type': 'nature reservation'}, + 'aux_images': [], 'aux_docs': [], 'aux_urls': [], 'globalmeta': {'climatic_zone_year2016': '6 (warm temperate dry)', 'distance_to_major_road_year2020': -999.0, 'dominant_ecoregion_year2017': '-1 (undefined)', @@ -640,7 +659,8 @@ class TestApps: 'mean_topography_srtm_alt_90m_year1994': -999.0, 'min_topography_srtm_relative_alt_5km_year1994': -999.0, 'stddev_topography_srtm_relative_alt_5km_year1994': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': []}, 'variable': {'name': 'toluene', 'longname': 'toluene', 'displayname': 'Toluene', 'cf_standardname': 'mole_fraction_of_toluene_in_air', 'units': 'nmol mol-1', @@ -654,7 +674,7 @@ class TestApps: response = client.get("/timeseries/1?fields=additional_metadata") expected_status_code = 200 assert response.status_code == expected_status_code - expected_response = {'additional_metadata': {} } + expected_response = {'additional_metadata': {'original_units': 'ppb'} } assert response.json() == expected_response @@ -663,7 +683,7 @@ class TestApps: expected_status_code = 200 assert response.status_code == expected_status_code expected_response = [ - {'additional_metadata': {}}, + {'additional_metadata': {'original_units': 'ppb'}}, {'additional_metadata': {'absorption_cross_section': 'Hearn 1961', 'ebas_metadata_19740101000000_29y': @@ -697,7 +717,7 @@ class TestApps: 'data_origin_type': 'measurement', 'provider_version': 'N/A', 'sampling_height': 7.0, - 'additional_metadata': {}, + 'additional_metadata': {'original_units': 'ppb'}, 'doi': '', 'coverage': -1.0, 'station': {'id': 2, @@ -710,9 +730,7 @@ class TestApps: 'type': 'unknown', 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', - 'additional_metadata': {'dummy_info': 'Here is some more information about the station'}, - 'roles': [], - 'annotations': [], + 'additional_metadata': {'add_type': 'nature reservation'}, 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -740,7 +758,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified' + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban' }, 'changelog': [] }, @@ -774,7 +793,17 @@ class TestApps: 'contact_url': 'mailto:immission@uba.de' } } - } + }, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}} ] }] assert response.json() == expected_response @@ -809,8 +838,6 @@ class TestApps: 'type_of_area': 'unknown', 'timezone': 'Asia/Shanghai', 'additional_metadata': {}, - 'roles': [], - 'annotations': [], 'aux_images': [], 'aux_docs': [], 'aux_urls': [], @@ -838,7 +865,8 @@ class TestApps: 'max_population_density_25km_year1990': -1.0, 'mean_nox_emissions_10km_year2015': -999.0, 'mean_nox_emissions_10km_year2000': -999.0, - 'toar1_category': 'unclassified'}, + 'toar1_category': 'unclassified', + 'toar2_category': 'suburban'}, 'changelog': []}, 'variable': {'name': 'o3', 'longname': 'ozone', @@ -985,7 +1013,18 @@ class TestApps: 'id': 2, 'role': 'resource provider', 'status': 'active' - } + }, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'organisation': None, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}} ] assert response.json() == expected_response @@ -994,7 +1033,7 @@ class TestApps: response = client.post("/timeseries/request_contributors?format=text", files={"file": open("tests/fixtures/timeseries/timeseries_id.txt", "rb")}) expected_status_code = 200 assert response.status_code == expected_status_code - expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt' + expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt; persons:Sabine Schröder' assert response.json() == expected_response @@ -1002,7 +1041,7 @@ class TestApps: response = client.post("/timeseries/request_contributors", files={"file": open("tests/fixtures/timeseries/timeseries_id.txt", "rb")}) expected_status_code = 200 assert response.status_code == expected_status_code - expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt' + expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt; persons:Sabine Schröder' assert response.json() == expected_response @@ -1011,8 +1050,101 @@ class TestApps: expected_status_code = 400 assert response.status_code == expected_status_code expected_response = 'not a valid format: CMOR' + assert response.json() == expected_response + + + def test_register_contributors_list(self, client, db): + response = client.post("/timeseries/register_timeseries_list_of_contributors/5f0df73a-bd0f-48b9-bb17-d5cd36f89598", + data='''[1,2]''', + headers={"email": "s.schroeder@fz-juelich.de"} ) + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = '5f0df73a-bd0f-48b9-bb17-d5cd36f89598 successfully registered.' assert response.json() == expected_response + + def test_register_duplicate_contributors_list(self, client, db): + response = client.post("/timeseries/register_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598", + data='''[1,2]''', + headers={"email": "s.schroeder@fz-juelich.de"} ) + expected_status_code = 443 + assert response.status_code == expected_status_code + expected_response = '7f0df73a-bd0f-48b9-bb17-d5cd36f89598 already registered.' + assert response.json() == expected_response + + + def test_request_registered_contributors_list_json(self, client, db): + response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=json") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = [ + {'contact': {'id': 5, + 'organisation': {'city': 'Jülich', + 'contact_url': 'mailto:toar-data@fz-juelich.de', + 'country': 'Germany', + 'homepage': 'https://www.fz-juelich.de', + 'id': 2, + 'kind': 'research', + 'longname': 'Forschungszentrum Jülich', + 'name': 'FZJ', + 'postcode': '52425', + 'street_address': 'Wilhelm-Johnen-Straße' + }, + 'person': None + }, + 'id': 1, + 'role': 'resource provider', + 'status': 'active' + }, + {'contact': {'id': 4, + 'organisation': {'city': 'Dessau-Roßlau', + 'contact_url': 'mailto:immission@uba.de', + 'country': 'Germany', + 'homepage': 'https://www.umweltbundesamt.de', + 'id': 1, + 'kind': 'government', + 'longname': 'Umweltbundesamt', + 'name': 'UBA', + 'postcode': '06844', + 'street_address': 'Wörlitzer Platz 1' + }, + 'person': None + }, + 'id': 2, + 'role': 'resource provider', + 'status': 'active' + }, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'organisation': None, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}} + ] + assert response.json() == expected_response + + + def test_request_registered_contributors_list_text(self, client, db): + response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-48b9-bb17-d5cd36f89598?format=text") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_response = 'organisations: Forschungszentrum Jülich;Umweltbundesamt; persons:Sabine Schröder' + assert response.json() == expected_response + + + def test_request_registered_contributors_list_unknown_rid(self, client, db): + response = client.get("/timeseries/request_timeseries_list_of_contributors/7f0df73a-bd0f-58b9-bb17-d5cd36f89598?format=text") + expected_status_code = 400 + assert response.status_code == expected_status_code + expected_response = 'not a registered request id: 7f0df73a-bd0f-58b9-bb17-d5cd36f89598' + assert response.json() == expected_response + + # 3. tests updating timeseries metadata def test_patch_timeseries_no_description(self, client, db): @@ -1140,12 +1272,22 @@ class TestApps: 'contact_url': 'mailto:immission@uba.de'}}, 'id': 2, 'role': 'resource provider', - 'status': 'active'}] + 'status': 'active'}, + {'id': 3, + 'role': 'principal investigator', + 'status': 'active', + 'contact': {'id': 3, + 'person': {'email': 's.schroeder@fz-juelich.de', + 'id': 3, + 'isprivate': False, + 'name': 'Sabine Schröder', + 'orcid': '0000-0002-0309-8010', + 'phone': '+49-2461-61-6397'}}}] set_expected_response_roles = {json.dumps(item, sort_keys=True) for item in response_roles} set_response_roles = {json.dumps(item, sort_keys=True) for item in response_json['roles']} assert set_response_roles == set_expected_response_roles - assert response_json['changelog'][0]['old_value'] == "{'roles': [{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 4}]}" - assert response_json['changelog'][0]['new_value'] == "{'roles': [{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 4}, {'role': 'ResourceProvider', 'contact_id': 5, 'status': 'Active'}]}" + assert response_json['changelog'][0]['old_value'] == "{'roles': [{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 4}, {'role': 'PrincipalInvestigator', 'status': 'Active', 'contact_id': 3}]}" + assert response_json['changelog'][0]['new_value'] == "{'roles': [{'role': 'ResourceProvider', 'status': 'Active', 'contact_id': 4}, {'role': 'PrincipalInvestigator', 'status': 'Active', 'contact_id': 3}, {'role': 'ResourceProvider', 'contact_id': 5, 'status': 'Active'}]}" assert response_json['changelog'][0]['author_id'] == 1 assert response_json['changelog'][0]['type_of_change'] == 'single value correction in metadata' diff --git a/tests/test_toardb.py b/tests/test_toardb.py index 61dae735bb85b9a7b7b429380ba8f921e04ba74b..6f09119904096a42fdbb18c9e5063a52138fa55d 100644 --- a/tests/test_toardb.py +++ b/tests/test_toardb.py @@ -12,12 +12,28 @@ from toardb.test_base import ( get_test_engine, test_db_session as db, ) +from toardb.stationmeta.models import StationmetaGlobalService class TestApps: def setup(self): self.application_url = "/controlled_vocabulary" + @pytest.fixture(autouse=True) + def setup_db_data(self, db): + # id_seq will not be reset automatically between tests! + _db_conn = get_test_engine() + infilename = "tests/fixtures/stationmeta/stationmeta_global_services.json" + with open(infilename) as f: + metajson=json.load(f) + for entry in metajson: + new_stationmeta_global_service = StationmetaGlobalService(**entry) + db.add(new_stationmeta_global_service) + db.commit() + db.refresh(new_stationmeta_global_service) + + + def test_get_controlled_vocabulary(self, client, db): response = client.get("/controlled_vocabulary") expected_status_code = 200 @@ -126,3 +142,134 @@ class TestApps: [210, 'Water', '210 (Water bodies)'], [220, 'SnowAndIce', '220 (Permanent snow and ice)']] assert response.json() == expected_resp + + + def test_get_controlled_vocabulary_unknown_field(self, client, db): + response = client.get("/controlled_vocabulary/Station Landuse Type") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = "No controlled vocabulary found for 'Station Landuse Type'" + assert response.json() == expected_resp + + + def test_get_database_statistics(self, client, db): + response = client.get("/database_statistics") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = {'data records': 65637800808, 'stations': 23979, 'time-series': 432880, 'users': 0} + assert response.json() == expected_resp + + + def test_get_database_statistics_field(self, client, db): + response = client.get("/database_statistics/stations") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = 23979 + assert response.json() == expected_resp + + + def test_get_geopeas_urls(self, client, db): + response = client.get("/geopeas_urls") + expected_status_code = 200 + assert response.status_code == expected_status_code + expected_resp = [ + { + "service_url":"{base_url}/climatic_zone/?lat={lat}&lon={lon}", + "variable_name":"climatic_zone_year2016" + }, + { + "service_url":"{base_url}/major_road/?lat={lat}&lon={lon}", + "variable_name":"distance_to_major_road_year2020" + }, + { + "service_url":"{base_url}/population_density/?year=2015&lat={lat}&lon={lon}", + "variable_name":"mean_population_density_250m_year2015" + }, + { + "service_url":"{base_url}/population_density/?year=2015&lat={lat}&lon={lon}", + "variable_name":"mean_population_density_5km_year2015" + }, + { + "service_url":"{base_url}/population_density/?agg=max&radius=25000&year=2015&lat={lat}&lon={lon}", + "variable_name":"max_population_density_25km_year2015" + }, + { + "service_url":"{base_url}/population_density/?agg=mean&radius=250&year=1990&lat={lat}&lon={lon}", + "variable_name":"mean_population_density_250m_year1990" + }, + { + "service_url":"{base_url}/population_density/?agg=mean&radius=5000&year=1990&lat={lat}&lon={lon}", + "variable_name":"mean_population_density_5km_year1990" + }, + { + "service_url":"{base_url}/population_density/?agg=max&radius=25000&year=1990&lat={lat}&lon={lon}", + "variable_name":"max_population_density_25km_year1990" + }, + { + "service_url":"{base_url}/nox_emissions/?year=2015&lat={lat}&lon={lon}", + "variable_name":"mean_nox_emissions_10km_year2015" + }, + { + "service_url":"{base_url}/nox_emissions/?year=2000&lat={lat}&lon={lon}", + "variable_name":"mean_nox_emissions_10km_year2000" + }, + { + "service_url":"{base_url}/htap_region_tier1/?lat={lat}&country={country}", + "variable_name":"htap_region_tier1_year2010" + }, + { + "service_url":"{base_url}/ecoregion/?description=false&lat={lat}&lon={lon}", + "variable_name":"dominant_ecoregion_year2017" + }, + { + "service_url":"{base_url}/landcover/?year=2012&description=false&lat={lat}&lon={lon}", + "variable_name":"dominant_landcover_year2012" + }, + { + "service_url":"{base_url}/ecoregion/?description=true&radius=25000&lat={lat}&lon={lon}", + "variable_name":"ecoregion_description_25km_year2017" + }, + { + "service_url":"{base_url}/landcover/?year=2012&radius=25000&description=true&lat={lat}&lon={lon}", + "variable_name":"landcover_description_25km_year2012" + }, + { + "service_url":"{base_url}/topography_srtm/?relative=false&lat={lat}&lon={lon}", + "variable_name":"mean_topography_srtm_alt_90m_year1994" + }, + { + "service_url":"{base_url}/topography_srtm/?relative=false&agg=mean&radius=1000&lat={lat}&lon={lon}", + "variable_name":"mean_topography_srtm_alt_1km_year1994" + }, + { + "service_url":"{base_url}/topography_srtm/?relative=true&agg=max&radius=5000&lat={lat}&lon={lon}", + "variable_name":"max_topography_srtm_relative_alt_5km_year1994" + }, + { + "service_url":"{base_url}/topography_srtm/?relative=true&agg=min&radius=5000&lat={lat}&lon={lon}", + "variable_name":"min_topography_srtm_relative_alt_5km_year1994" + }, + { + "service_url":"{base_url}/topography_srtm/?relative=true&agg=stddev&radius=5000&lat={lat}&lon={lon}", + "variable_name":"stddev_topography_srtm_relative_alt_5km_year1994" + }, + { + "service_url":"{base_url}/stable_nightlights/?year=2013&lat={lat}&lon={lon}", + "variable_name":"mean_stable_nightlights_1km_year2013" + }, + { + "service_url":"{base_url}/stable_nightlights/?agg=mean&radius=5000&year=2013&lat={lat}&lon={lon}", + "variable_name":"mean_stable_nightlights_5km_year2013" + }, + { + "service_url":"{base_url}/stable_nightlights/?agg=max&radius=25000&year=2013&lat={lat}&lon={lon}", + "variable_name":"max_stable_nightlights_25km_year2013" + }, + { + "service_url":"{base_url}/stable_nightlights/?agg=max&radius=25000&year=1992&lat={lat}&lon={lon}", + "variable_name":"max_stable_nightlights_25km_year1992" + } + ] + set_expected_resp = {json.dumps(item, sort_keys=True) for item in expected_resp} + set_response = {json.dumps(item, sort_keys=True) for item in response.json()} + assert set_response == set_expected_resp diff --git a/toardb/data/data.py b/toardb/data/data.py index 6f5969fc9ed7536859428e26a2b393caea298d2d..a8a893ef7c455b9e47fb8dfd97a2a193aacc214b 100644 --- a/toardb/data/data.py +++ b/toardb/data/data.py @@ -33,7 +33,7 @@ def get_all_data(offset: int = 0, limit: int = 100, db: Session = Depends(get_db #get all data of one timeseries -@router.get('/data/timeseries/{timeseries_id}', response_model=schemas.Composite, response_model_exclude_unset=True) +@router.get('/data/timeseries/{timeseries_id}', response_model=schemas.Composite, response_model_exclude_unset=True, response_model_exclude_none=True) def get_data(timeseries_id: int, request: Request, db: Session = Depends(get_db)): db_data = crud.get_data(db, timeseries_id=timeseries_id, path_params=request.path_params, query_params=request.query_params) if db_data is None: @@ -49,7 +49,7 @@ def get_version(timeseries_id: int, request: Request, db: Session = Depends(get_ #get all data of one timeseries -@router.get('/data/timeseries/id/{timeseries_id}', response_model=schemas.Composite, response_model_exclude_unset=True) +@router.get('/data/timeseries/id/{timeseries_id}', response_model=schemas.Composite, response_model_exclude_unset=True, response_model_exclude_none=True) def get_data2(timeseries_id: int, request: Request, db: Session = Depends(get_db)): db_data = crud.get_data(db, timeseries_id=timeseries_id, path_params=request.path_params, query_params=request.query_params) if db_data is None: @@ -58,7 +58,7 @@ def get_data2(timeseries_id: int, request: Request, db: Session = Depends(get_db #get all data of one timeseries (including staging data) -@router.get('/data/timeseries_with_staging/id/{timeseries_id}', response_model=schemas.Composite, response_model_exclude_unset=True) +@router.get('/data/timeseries_with_staging/id/{timeseries_id}', response_model=schemas.Composite, response_model_exclude_unset=True, response_model_exclude_none=True) def get_data_with_staging(timeseries_id: int, flags: str = None, format: str = 'json', db: Session = Depends(get_db)): db_data = crud.get_data_with_staging(db, timeseries_id=timeseries_id, flags=flags, format=format) if db_data is None: diff --git a/toardb/stationmeta/crud.py b/toardb/stationmeta/crud.py index 2c1f8694c2e9578e428a73cac1e466051da97a20..e0fe66ceb5d1f1a18e2104dd0921fb9929194118 100644 --- a/toardb/stationmeta/crud.py +++ b/toardb/stationmeta/crud.py @@ -210,6 +210,11 @@ def get_unique_stationmeta_annotation(db: Session, text: str, contributor_id: in return db_object +# is this internal, or should this also go to public REST api? +def get_stationmeta_annotations(db: Session, station_id: int): + return db.execute(select([stationmeta_core_stationmeta_annotations_table]).where(stationmeta_core_stationmeta_annotations_table.c.station_id == station_id)) + + def get_stationmeta_global(db: Session, station_id: int): db_object = db.query(models.StationmetaGlobal).filter(models.StationmetaGlobal.station_id == station_id) \ .first() @@ -243,6 +248,7 @@ def determine_stationmeta_global(db, tmp_coordinates, country): globalmeta_dict[db_service.variable_name] = value return globalmeta_dict + def create_stationmeta(db: Session, engine: Engine, stationmeta: StationmetaCreate, author_id: int, force: bool): stationmeta_dict = stationmeta.dict() @@ -503,6 +509,7 @@ def patch_stationmeta(db: Session, description: str, number_of_commas = number_of_commas - 1 for a in annotations_data: db_annotation = models.StationmetaAnnotation(**a) + db_annotation.kind = get_value_from_str(toardb.toardb.AK_vocabulary,db_annotation.kind) # check whether annotation is already present in database db_object = get_unique_stationmeta_annotation(db, db_annotation.text, db_annotation.contributor_id) if db_object: @@ -556,6 +563,9 @@ def patch_stationmeta(db: Session, description: str, elif key == "toar1_category": value = get_value_from_str(toardb.toardb.TC_vocabulary, value) old_value = get_str_from_value(toardb.toardb.TC_vocabulary, old_value) + elif key == "toar2_category": + value = get_value_from_str(toardb.toardb.TA_vocabulary, value) + old_value = get_str_from_value(toardb.toardb.TA_vocabulary, old_value) elif key == "htap_region_tier1_year2010": value = get_value_from_str(toardb.toardb.TR_vocabulary, value) old_value = get_str_from_value(toardb.toardb.TR_vocabulary, old_value) @@ -598,13 +608,15 @@ def delete_stationmeta_field(db: Session, station_id: int, field: str, author_id # id can never be deleted (and of course also not changed)!!! # there are mandatory fields (from stationmeta_core), that cannot be deleted! # --> set these to their default value + new_value = "" field_table = {'roles': stationmeta_core_stationmeta_roles_table, 'annotations': stationmeta_core_stationmeta_annotations_table} # 'aux_images': stationmeta_core_stationmeta_aux_images_table, # 'aux_docs': stationmeta_core_stationmeta_aux_docs_table, # 'aux_urls': stationmeta_core_stationmeta_aux_urls_table, - if ((field == 'roles') or (field == 'annotations')): + if (field in field_table): db.execute(delete(field_table[field]).where(field_table[field].c.station_id==station_id)) + new_value = f"'{field}': []" # problem with automatic conversion of coordinates (although not explicitly fetched from database) # ==> next two lines are a workaround db_stationmeta = db.query(models.StationmetaCore).get(station_id) @@ -615,11 +627,11 @@ def delete_stationmeta_field(db: Session, station_id: int, field: str, author_id description=f"delete field {field}" old_value = get_field_from_record(db, station_id, field, db_stationmeta) db_changelog = StationmetaChangelog(description=description, station_id=station_id, author_id=author_id, type_of_change=type_of_change, - old_value=old_value, new_value="") + old_value=old_value, new_value=new_value) db.add(db_changelog) db.commit() # there's a mismatch with coordinates --> how to automatically switch back and forth?! db_stationmeta.coordinates = tmp_coordinates # hotfix: db_stationmeta.global needs to be retranslated to the dict that is understood by StationmetaGlobal - db_stationmeta.globalmeta= None + db_stationmeta.globalmeta = None return db_stationmeta diff --git a/toardb/stationmeta/models_global.py b/toardb/stationmeta/models_global.py index cebd9b8165565c0eb7b1a3c2e94d2652b647bc44..2bc33636b293dcfb131eb44032700a44074546b3 100644 --- a/toardb/stationmeta/models_global.py +++ b/toardb/stationmeta/models_global.py @@ -72,6 +72,8 @@ class StationmetaGlobal(Base): +--------------------------------------------------+------------------------+-----------+----------+------------------------------------------------+ | toar1_category | integer | | not null | '-1'::integer | +--------------------------------------------------+------------------------+-----------+----------+------------------------------------------------+ + | toar2_category | integer | | not null | '0'::integer | + +--------------------------------------------------+------------------------+-----------+----------+------------------------------------------------+ | station_id | integer | | not null | | +--------------------------------------------------+------------------------+-----------+----------+------------------------------------------------+ @@ -82,7 +84,6 @@ class StationmetaGlobal(Base): "stationmeta_global_climatic_zone_check" CHECK (climatic_zone_year2016 >= 0) "stationmeta_global_dominant_landcover_year2012_check" CHECK (dominant_landcover_year2012 >= 0) "stationmeta_global_htap_region_tier1_check" CHECK (htap_region_tier1_year2010 >= 0) - "stationmeta_global_toar1_category_check" CHECK (toar1_category >= 0) Foreign-key constraints: "stationmeta_global_station_id_29ff53dd_fk_stationmeta_core_id" FOREIGN KEY (station_id) REFERENCES stationmeta_core(id) DEFERRABLE INITIALLY DEFERRED "stationmeta_glob_dominant_ecoregion_year2017_fk_er_voc_enum_val" FOREIGN KEY (dominant_ecoregion_year2017) REFERENCES er_vocabulary(enum_val) @@ -90,6 +91,7 @@ class StationmetaGlobal(Base): "stationmeta_global_climatic_zone_fk_cz_at_vocabulary_enum_val" FOREIGN KEY (climatic_zone_year2016) REFERENCES cz_vocabulary(enum_val) "stationmeta_global_htap_region_tier1_fk_tr_vocabulary_enum_val" FOREIGN KEY (htap_region_tier1_year2010) REFERENCES tr_vocabulary(enum_val) "stationmeta_global_toar1_category_fk_tc_vocabulary_enum_val" FOREIGN KEY (toar1_category) REFERENCES tc_vocabulary(enum_val) + "stationmeta_global_toar2_category_fk_tc_vocabulary_enum_val" FOREIGN KEY (toar2_category) REFERENCES ta_vocabulary(enum_val) """ __tablename__ = 'stationmeta_global' # Default values do not fit CheckConstraints == > Check, how both can be done!! @@ -97,7 +99,6 @@ class StationmetaGlobal(Base): # CheckConstraint('climatic_zone_year2016 >= 0'), # CheckConstraint('dominant_landcover_year2012 >= 0'), # CheckConstraint('htap_region_tier1_year2010 >= 0'), -# CheckConstraint('toar1_category >= 0') # ) id = Column(Integer, STATIONMETA_GLOBAL_ID_SEQ, primary_key=True, server_default=STATIONMETA_GLOBAL_ID_SEQ.next_value()) @@ -126,6 +127,7 @@ class StationmetaGlobal(Base): mean_nox_emissions_10km_year2015 = Column(Float(53), nullable=False, server_default=text("'-999.0'::numeric")) mean_nox_emissions_10km_year2000 = Column(Float(53), nullable=False, server_default=text("'-999.0'::numeric")) toar1_category = Column(ForeignKey('tc_vocabulary.enum_val'), nullable=False, server_default=text("'-1'::integer")) + toar2_category = Column(ForeignKey('ta_vocabulary.enum_val'), nullable=False, server_default=text("'0'::integer")) # do not use string declaration here (not working for pytest) # use the explicit class name here, # see: https://groups.google.com/forum/#!topic/sqlalchemy/YjGhE4d6K4U diff --git a/toardb/stationmeta/schemas.py b/toardb/stationmeta/schemas.py index f544b448c3d43b18559a2ebf2cdc988167fe4746..7b5333824bd5f92a88d6fd1a11517247068a4292 100644 --- a/toardb/stationmeta/schemas.py +++ b/toardb/stationmeta/schemas.py @@ -297,6 +297,7 @@ class StationmetaGlobalBase(BaseModel): mean_nox_emissions_10km_year2015: float = Field(..., description="mean value within a radius of 10 km around station location of the following data of the year 2015: " + str(provenance['nox_emissions'])) mean_nox_emissions_10km_year2000: float = Field(..., description="mean value within a radius of 10 km around station location of the following data of the year 2000: " + str(provenance['nox_emissions'])) toar1_category: str = Field(..., description="The station classification for the Tropsopheric Ozone Assessment Report based on the station proxy data that are stored in the TOAR database (see controlled vocabulary: Station TOAR Category)") + toar2_category: str = Field(..., description="The station classification for the TOAR-II based on the station proxy data that are stored in the TOAR database and obtained from an ML approach (see controlled vocabulary: Station TOAR Category)") # station_id: int = Field(..., description="internal station_id to which these global data belong") @@ -308,6 +309,10 @@ class StationmetaGlobalBase(BaseModel): def check_toar1_category(cls, v): return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TC_vocabulary))[0].display_str + @validator('toar2_category') + def check_toar2_category(cls, v): + return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TA_vocabulary))[0].display_str + @validator('htap_region_tier1_year2010') def check_htap_region_tier1(cls, v): return tuple(filter(lambda x: x.value == int(v), toardb.toardb.TR_vocabulary))[0].display_str @@ -355,6 +360,7 @@ class StationmetaGlobalPatch(BaseModel): mean_nox_emissions_10km_year2015: float = None mean_nox_emissions_10km_year2000: float = None toar1_category: str = None + toar2_category: str = None station_id: int = None @validator('climatic_zone_year2016') @@ -371,6 +377,13 @@ class StationmetaGlobalPatch(BaseModel): else: raise ValueError(f"TOAR1 category not known: {v}") + @validator('toar2_category') + def check_toar2_category(cls, v): + if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)): + return v + else: + raise ValueError(f"TOAR2 category not known: {v}") + @validator('htap_region_tier1_year2010') def check_htap_region_tier1(cls, v): if tuple(filter(lambda x: x.string == v, toardb.toardb.TR_vocabulary)): @@ -459,6 +472,10 @@ class StationmetaGlobalFields(StationmetaGlobalPatch): def check_toar1_category(cls, v): return v + @validator('toar2_category') + def check_toar2_category(cls, v): + return v + @validator('htap_region_tier1_year2010') def check_htap_region_tier1(cls, v): return v @@ -497,6 +514,13 @@ class StationmetaGlobalCreate(StationmetaGlobalBase): else: raise ValueError(f"TOAR1 category not known: {v}") + @validator('toar2_category') + def check_toar2_category(cls, v): + if tuple(filter(lambda x: x.string == v, toardb.toardb.TA_vocabulary)): + return v + else: + raise ValueError(f"TOAR2 category not known: {v}") + @validator('htap_region_tier1_year2010') def check_htap_region_tier1(cls, v): if tuple(filter(lambda x: x.string == v, toardb.toardb.TR_vocabulary)): @@ -551,6 +575,7 @@ class StationmetaGlobalBaseNested(StationmetaGlobalBase): mean_nox_emissions_10km_year2015: float = None mean_nox_emissions_10km_year2000: float = None toar1_category: str = None + toar2_category: str = None class StationmetaGlobalNestedCreate(StationmetaGlobalBaseNested): @@ -688,10 +713,27 @@ class StationmetaBase(StationmetaCoreBase): def order_changelog(cls, v): return sorted(v, key=lambda x: x.datetime) + @validator('roles') + def check_roles(cls, v): + if v == []: + return None + else: + return v -class StationmetaPatch(StationmetaCorePatch): - roles: List[StationmetaRolePatch] = None - annotations: List[StationmetaAnnotationPatch] = None + @validator('annotations') + def check_annotations(cls, v): + if v == []: + return None + else: + return v + + +class StationmetaPatch(StationmetaCoreCreate): + #roles: List[StationmetaRolePatch] = None + #annotations: List[StationmetaAnnotationPatch] = None + # just to get things working + roles: list = None + annotations: list = None aux_images: List[StationmetaAuxImagePatch] = None aux_docs: List[StationmetaAuxDocPatch] = None aux_urls: List[StationmetaAuxUrlPatch] = None diff --git a/toardb/stationmeta/stationmeta.py b/toardb/stationmeta/stationmeta.py index 29670491c0357162fa2bc8ad986a2bea9a7b97e5..2809f263507e661a052009f99d5637e75c252995 100644 --- a/toardb/stationmeta/stationmeta.py +++ b/toardb/stationmeta/stationmeta.py @@ -38,7 +38,7 @@ def get_stationmeta(station_code: str, fields: str = None, db: Session = Depends #get all core metadata of one station (given its ID) -@router.get('/stationmeta/id/{station_id}', response_model=schemas.Stationmeta) +@router.get('/stationmeta/id/{station_id}', response_model=schemas.Stationmeta, response_model_exclude_none=True, response_model_exclude_unset=True) def get_stationmeta_by_id(station_id: int, db: Session = Depends(get_db)): db_stationmeta = crud.get_stationmeta_by_id(db, station_id=station_id) if db_stationmeta is None: @@ -78,7 +78,8 @@ async def create_stationmeta_core(request: Request, # 3. update -@router.patch('/stationmeta/{station_code}', response_model=schemas.StationmetaPatch) +#@router.patch('/stationmeta/{station_code}', response_model=schemas.StationmetaPatch) +@router.patch('/stationmeta/{station_code}') def patch_stationmeta_core(request: Request, station_code: str, stationmeta: schemas.StationmetaPatch = Body(..., embed = True), @@ -107,8 +108,8 @@ def patch_stationmeta_core(request: Request, raise HTTPException(status_code=401, detail="Unauthorized.") -@router.patch('/stationmeta/delete_field/{station_code}', response_model=schemas.StationmetaPatch) -def patch_stationmeta_core(request: Request, +@router.patch('/stationmeta/delete_field/{station_code}', response_model=schemas.StationmetaBase, response_model_exclude_none=True, response_model_exclude_unset=True) +def delete_field_from_stationmeta_core(request: Request, station_code: str, field: str, access: dict = Depends(get_station_md_change_access_rights), diff --git a/toardb/timeseries/crud.py b/toardb/timeseries/crud.py index a2c16a996fc712357e82bf67f03e21eadf8cf013..3449b2d2526915266c0bab0b43bcb88fb46e42cb 100644 --- a/toardb/timeseries/crud.py +++ b/toardb/timeseries/crud.py @@ -9,6 +9,7 @@ Create, Read, Update, Delete functionality from sqlalchemy import insert, select, and_, func, text from sqlalchemy.orm import Session, load_only from sqlalchemy.util import _collections +from sqlalchemy.exc import IntegrityError from geoalchemy2.elements import WKBElement, WKTElement from fastapi import File, UploadFile from fastapi.responses import JSONResponse @@ -16,7 +17,7 @@ import datetime as dt import json from . import models from .models import TimeseriesChangelog, timeseries_timeseries_roles_table, \ - timeseries_timeseries_annotations_table + timeseries_timeseries_annotations_table, s1_contributors_table from toardb.stationmeta.models import StationmetaCore, StationmetaGlobal from toardb.stationmeta.schemas import get_coordinates_from_geom, get_geom_from_coordinates, get_coordinates_from_string from toardb.stationmeta.crud import get_stationmeta_by_id, get_stationmeta_core, station_id_exists, get_stationmeta_changelog @@ -578,9 +579,7 @@ def get_contributors_string(programmes, roles): return result -def get_request_contributors(db: Session, format: str = 'text', input_handle: UploadFile = File(...)): - f = input_handle.file - timeseries_ids = [int(line.strip()) for line in f.readlines()] +def get_contributors_list(db: Session, timeseries_ids, format: str = 'text'): # get time series' programmes # join(models.Timeseries, Timeseries.programme_id == TimeseriesProgramme.id) is implicit given due to the foreign key programmes = db.query(models.TimeseriesProgramme) \ @@ -604,6 +603,41 @@ def get_request_contributors(db: Session, format: str = 'text', input_handle: Up return result +def get_request_contributors(db: Session, format: str = 'text', input_handle: UploadFile = File(...)): + f = input_handle.file + timeseries_ids = [int(line.strip()) for line in f.readlines()] + return get_contributors_list(db, timeseries_ids, format) + + +def get_registered_request_contributors(db: Session, rid, format: str = 'text'): + try: + timeseries_ids = db.execute(select([s1_contributors_table]).\ + where(s1_contributors_table.c.request_id == rid)).mappings().first()['timeseries_ids'] + return get_contributors_list(db, timeseries_ids, format) + except: + status_code=400 + message=f"not a registered request id: {rid}" + return JSONResponse(status_code=status_code, content=message) + + +def register_request_contributors(db: Session, rid, ids): + try: + db.execute(insert(s1_contributors_table).values(request_id=rid, timeseries_ids=ids)) + db.commit() + status_code = 200 + message=f'{rid} successfully registered.' + except IntegrityError as e: + error_code = e.orig.pgcode + if error_code == "23505": + status_code = 443 + message=f'{rid} already registered.' + else: + status_code = 442 + message=f'database error: error_code' + result = JSONResponse(status_code=status_code, content=message) + return result + + # is this internal, or should this also go to public REST api? def get_timeseries_roles(db: Session, timeseries_id: int): return db.execute(select([timeseries_timeseries_roles_table]).where(timeseries_timeseries_roles_table.c.timeseries_id == timeseries_id)) diff --git a/toardb/timeseries/models.py b/toardb/timeseries/models.py index f697fb36f5cdbdf98bf706d56bb027353e985fe7..121bcc7b953392e875d8240e46e32bcf373e5c33 100644 --- a/toardb/timeseries/models.py +++ b/toardb/timeseries/models.py @@ -6,6 +6,7 @@ from .models_role import TimeseriesRole, timeseries_timeseries_roles_table from .models_annotation import TimeseriesAnnotation, timeseries_timeseries_annotations_table from .models_programme import TimeseriesProgramme from .models_changelog import TimeseriesChangelog +from .models_contributor import s1_contributors_table from toardb.base import Base from sqlalchemy import Table, Column, Integer, String diff --git a/toardb/timeseries/models_contributor.py b/toardb/timeseries/models_contributor.py new file mode 100644 index 0000000000000000000000000000000000000000..de4ccb5f4ca03f96f77c53aafe43607f14daeee9 --- /dev/null +++ b/toardb/timeseries/models_contributor.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021 Forschungszentrum Jülich GmbH +# SPDX-License-Identifier: MIT + +""" +class TimeseriesContributorList (Base) +====================================== +""" +from sqlalchemy import Column, Integer, String, ARRAY, Table +from toardb.base import Base + + +s1_contributors_table = Table('s1_contributors', Base.metadata, + Column('request_id', String(36), primary_key=True, nullable=False), + Column('timeseries_ids',ARRAY(Integer)) +) + diff --git a/toardb/timeseries/timeseries.py b/toardb/timeseries/timeseries.py index 2a5d8005ca91a94ddc97e363a0a29a191429e96d..84630ee4079b426283f1585131489f2b9ff4b442 100644 --- a/toardb/timeseries/timeseries.py +++ b/toardb/timeseries/timeseries.py @@ -25,7 +25,8 @@ from toardb.data.models import Data from toardb.utils.utils import ( get_str_from_value, get_admin_access_rights, - get_timeseries_md_change_access_rights + get_timeseries_md_change_access_rights, + get_register_contributors_access_rights ) @@ -109,17 +110,33 @@ def get_timeseries(station_id: int, variable_id: int, resource_provider: str = N raise HTTPException(status_code=404, detail="Timeseries not found.") return db_timeseries + @router.get('/timeseries_changelog/{timeseries_id}', response_model=List[schemas.TimeseriesChangelog]) def get_timeseries_changelog(timeseries_id: int, db: Session = Depends(get_db)): db_changelog = crud.get_timeseries_changelog(db, timeseries_id=timeseries_id) return db_changelog + @router.get('/timeseries_programme/{name}', response_model=List[schemas.TimeseriesProgramme]) def get_timeseries_programme(name: str, db: Session = Depends(get_db)): db_programme = crud.get_timeseries_programme(db, name=name) return db_programme +@router.get('/timeseries/request_timeseries_list_of_contributors/{rid}', response_model=Union[str,List[schemas.Contributors]]) +def get_registered_request_contributors(rid: str, format: str = 'text', db: Session = Depends(get_db)): + contributors = crud.get_registered_request_contributors(db, rid=rid, format=format) + return contributors + + +@router.post('/timeseries/register_timeseries_list_of_contributors/{rid}') +def register_request_contributors(rid: str, + ids: List[int], + access: dict = Depends(get_register_contributors_access_rights), + db: Session = Depends(get_db)): + return crud.register_request_contributors(db, rid=rid, ids=ids) + + @router.post('/timeseries/request_contributors', response_model=Union[str,List[schemas.Contributors]]) def get_request_contributors(format: str = 'text', file: UploadFile = File(...), db: Session = Depends(get_db)): contributors = crud.get_request_contributors(db, format=format, input_handle=file) diff --git a/toardb/toardb.py b/toardb/toardb.py index 962d5047e1a283de4d70d2a5bff97ea6d019f0aa..a779130a2151ace9ed56b466bcb75474de39c3bf 100644 --- a/toardb/toardb.py +++ b/toardb/toardb.py @@ -76,7 +76,7 @@ async def profile_request(request: Request, call_next): "html": HTMLRenderer, "json": SpeedscopeRenderer, } - if request.query_params.get("profile", False): + if request.query_params.get("profile", False): # pragma: no cover current_dir = Path(__file__).parent profile_type = request.query_params.get("profile_format", "html") with Profiler(interval=0.001, async_mode="enabled") as profiler: @@ -92,7 +92,7 @@ async def profile_request(request: Request, call_next): # check more on https://fastapi.tiangolo.com/tutorial/middleware/ @app.middleware("http") async def add_process_time_header(request: Request, call_next): - if request.query_params.get("timing", False): + if request.query_params.get("timing", False): # pragma: no cover current_dir = Path(__file__).parent start_time = time.time() response = await call_next(request) diff --git a/toardb/utils/utils.py b/toardb/utils/utils.py index 02a330309a855671399096621cc3b606f4bb05a4..3b557d86adbccdd159b2271af8a58f1e9d068350 100644 --- a/toardb/utils/utils.py +++ b/toardb/utils/utils.py @@ -16,6 +16,8 @@ import requests import datetime as dt from toardb.utils.settings import base_geopeas_url, userinfo_endpoint + +# the following statement only if not in testing (pytest) mode! from toardb.utils.database import get_db from toardb.contacts.models import Contact, Organisation, Person from toardb.timeseries.models import Timeseries, TimeseriesRole, timeseries_timeseries_roles_table @@ -38,7 +40,7 @@ def get_access_rights(request: Request, access_right: str = 'admin'): if status_code != 401: userinfo = userinfo.json() if "eduperson_entitlement" in userinfo and \ - f"urn:geant:helmholtz.de:res:toar-data:{access_right}#login-dev.helmholtz.de" \ + f"urn:geant:helmholtz.de:res:toar-data{access_right}#login.helmholtz.de" \ in userinfo["eduperson_entitlement"]: user_name = userinfo["name"] user_email = userinfo["email"] @@ -59,20 +61,24 @@ def get_access_rights(request: Request, access_right: str = 'admin'): def get_admin_access_rights(request: Request): - return get_access_rights(request, 'admin') + return get_access_rights(request, ':admin') def get_station_md_change_access_rights(request: Request): - return get_access_rights(request, 'station-md-change') + return get_access_rights(request, ':station-md-change') def get_timeseries_md_change_access_rights(request: Request): - return get_access_rights(request, 'timeseries-md-change') + return get_access_rights(request, ':timeseries-md-change') def get_data_change_access_rights(request: Request): - return get_access_rights(request, 'data-change') + return get_access_rights(request, ':data-change') + + +def get_register_contributors_access_rights(request: Request): + return get_access_rights(request, ':contributors-register') # function to return code for given value @@ -138,15 +144,16 @@ def create_filter(query_params, endpoint): # determine allowed query parameters (first *only* from Timeseries) timeseries_params = {column.name for column in inspect(Timeseries).c} | {"has_role"} + timeseries_params = timeseries_params | {"additional_metadata-"} gis_params = {"bounding_box", "altitude_range"} if endpoint in ['search', 'timeseries']: core_params = {column.name for column in inspect(StationmetaCore).c if column.name not in ['id']} else: core_params = {column.name for column in inspect(StationmetaCore).c} - core_params |= {"globalmeta"} + core_params |= {"globalmeta", "station_additional_metadata-"} global_params = {column.name for column in inspect(StationmetaGlobal).c if column.name not in ['id','station_id']} data_params = {column.name for column in inspect(Data).c} | {"daterange", "format"} - ambig_params = {"station_id", "station_changelog", "station_country"} + ambig_params = {"station_id", "station_changelog", "station_country", "station_additional_metadata"} variable_params = {column.name for column in inspect(Variable).c} person_params = {column.name for column in inspect(Person).c} allrel_params = {"limit", "offset", "fields", "format"} @@ -198,12 +205,13 @@ def create_filter(query_params, endpoint): v_filter = [] p_filter = [] # query_params is a multi-dict! - for param in query_params: + for param_long in query_params: + param = param_long.split('>')[0] if param not in allowed_params: #inform user, that an unknown parameter name was used (this could be a typo and falsify the result!) raise KeyError(f"An unknown argument was received: {param}.") if param in allrel_params or param in profiling_params: continue - values = [item.strip() for v in query_params.getlist(param) for item in v.split(',')] + values = [item.strip() for v in query_params.getlist(param_long) for item in v.split(',')] # make sure ids are ints if param.endswith("id"): try: @@ -229,6 +237,8 @@ def create_filter(query_params, endpoint): values = translate_convoc_list(values, toardb.toardb.ST_vocabulary, "type") elif param == "type_of_area": values = translate_convoc_list(values, toardb.toardb.TA_vocabulary, "type of area") + elif param == "station_additional_metadata-": + param = f"{param_long[8:]}" # exceptions for special fields (codes, name) if param == 'codes': tmp_filter = [] @@ -238,6 +248,10 @@ def create_filter(query_params, endpoint): s_c_filter.append(f"({tmp_filter})") elif param == 'name': s_c_filter.append(f"LOWER(stationmeta_core.name) LIKE '%{values[0].lower()}%'") + elif param_long.split('>')[0] == "station_additional_metadata-": + val_mod = [ f"'\"{val}\"'::text" for val in values ] + values = ",".join(val_mod) + s_c_filter.append(f"to_json(stationmeta_core.{param})::text IN ({values})") else: s_c_filter.append(f"stationmeta_core.{param} IN {values}") elif endpoint in ["stationmeta", "search"] and param in global_params: @@ -269,6 +283,24 @@ def create_filter(query_params, endpoint): values = translate_convoc_list(values, toardb.toardb.OT_vocabulary, "data origin type") elif param == "data_origin": values = translate_convoc_list(values, toardb.toardb.DO_vocabulary, "data origin") + elif param == "additional_metadata-": + param = param_long + if param == "additional_metadata->'absorption_cross_section'": + trlist = translate_convoc_list(values, toardb.toardb.CS_vocabulary, "absorption_cross_section") + values = [ str(val) for val in trlist ] + param = f"timeseries.{param}" + elif param == "additional_metadata->'sampling_type'": + trlist = translate_convoc_list(values, toardb.toardb.KS_vocabulary, "sampling_type") + values = [ str(val) for val in trlist ] + param = f"timeseries.{param}" + elif param == "additional_metadata->'calibration_type'": + trlist = translate_convoc_list(values, toardb.toardb.CT_vocabulary, "calibration_type") + values = [ str(val) for val in trlist ] + param = f"timeseries.{param}" + else: + val_mod = [ f"'\"{val}\"'::text" for val in values ] + values = "(" + ",".join(val_mod) + ")" + param = f"to_json(timeseries.{param})::text" if param == "has_role": operator = "IN" join_operator = "OR" @@ -285,6 +317,8 @@ def create_filter(query_params, endpoint): t_r_filter.append(f"persons.name {operator} {values}") t_r_filter.append(f"persons.orcid {operator} {values}") t_r_filter = f" {join_operator} ".join(t_r_filter) + elif param_long.split('>')[0] == "additional_metadata-": + t_filter.append(f"{param} IN {values}") else: t_filter.append(f"timeseries.{param} IN {values}") elif param in data_params: