hestia-earth-models 0.64.11__py3-none-any.whl → 0.64.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of hestia-earth-models might be problematic. Click here for more details.
- hestia_earth/models/aware/scarcityWeightedWaterUse.py +1 -1
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandOccupation.py +1 -1
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsLandTransformation.py +1 -1
- hestia_earth/models/chaudharyBrooks2018/damageToTerrestrialEcosystemsTotalLandUseEffects.py +3 -2
- hestia_earth/models/cml2001Baseline/eutrophicationPotentialExcludingFate.py +2 -2
- hestia_earth/models/cml2001Baseline/terrestrialAcidificationPotentialIncludingFateAverageEurope.py +2 -2
- hestia_earth/models/cml2001NonBaseline/eutrophicationPotentialIncludingFateAverageEurope.py +2 -2
- hestia_earth/models/cml2001NonBaseline/terrestrialAcidificationPotentialExcludingFate.py +2 -2
- hestia_earth/models/cycle/completeness/seed.py +6 -4
- hestia_earth/models/cycle/concentrateFeed.py +36 -19
- hestia_earth/models/cycle/endDate.py +10 -1
- hestia_earth/models/cycle/milkYield.py +6 -5
- hestia_earth/models/cycle/startDate.py +6 -4
- hestia_earth/models/edip2003/ozoneDepletionPotential.py +2 -2
- hestia_earth/models/emissionNotRelevant/__init__.py +3 -2
- hestia_earth/models/environmentalFootprintV3/freshwaterEcotoxicityPotentialCtue.py +2 -3
- hestia_earth/models/faostat2018/utils.py +72 -12
- hestia_earth/models/hestia/__init__.py +13 -0
- hestia_earth/models/hestia/landCover.py +727 -0
- hestia_earth/models/ipcc2013ExcludingFeedbacks/gwp100.py +2 -2
- hestia_earth/models/ipcc2013IncludingFeedbacks/gwp100.py +2 -2
- hestia_earth/models/ipcc2019/aboveGroundBiomass.py +4 -8
- hestia_earth/models/ipcc2019/animal/fatContent.py +1 -1
- hestia_earth/models/ipcc2019/animal/milkYieldPerAnimal.py +91 -0
- hestia_earth/models/ipcc2019/animal/trueProteinContent.py +1 -1
- hestia_earth/models/ipcc2019/animal/utils.py +17 -12
- hestia_earth/models/ipcc2019/belowGroundBiomass.py +4 -8
- hestia_earth/models/ipcc2019/biomass_utils.py +11 -0
- hestia_earth/models/ipcc2019/ch4ToAirEntericFermentation.py +1 -1
- hestia_earth/models/ipcc2019/co2ToAirAboveGroundBiomassStockChange.py +8 -4
- hestia_earth/models/ipcc2019/co2ToAirBelowGroundBiomassStockChange.py +7 -3
- hestia_earth/models/ipcc2019/co2ToAirCarbonStockChange_utils.py +52 -6
- hestia_earth/models/ipcc2019/co2ToAirSoilOrganicCarbonStockChange.py +7 -3
- hestia_earth/models/ipcc2019/n2OToAirExcretaDirect.py +14 -9
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_1_utils.py +9 -3
- hestia_earth/models/ipcc2019/organicCarbonPerHa_tier_2_utils.py +1 -2
- hestia_earth/models/koble2014/aboveGroundCropResidue.py +5 -1
- hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsClimateChange.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsFreshwaterEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactAllEffects100Years/damageToFreshwaterEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealth.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthClimateChange.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthHumanToxicityCancerogenic.py +2 -3
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthHumanToxicityNonCancerogenic.py +2 -3
- hestia_earth/models/lcImpactAllEffects100Years/damageToHumanHealthStratosphericOzoneDepletion.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToMarineEcosystemsMarineEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactAllEffects100Years/damageToMarineEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsClimateChange.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactAllEffects100Years/damageToTerrestrialEcosystemsTerrestrialEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsClimateChange.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToFreshwaterEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealth.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthClimateChange.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthHumanToxicityCancerogenic.py +2 -3
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthHumanToxicityNonCancerogenic.py +2 -3
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToHumanHealthStratosphericOzoneDepletion.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToMarineEcosystemsMarineEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToMarineEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsClimateChange.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactAllEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsFreshwaterEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactCertainEffects100Years/damageToFreshwaterEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealth.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthClimateChange.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthHumanToxicityCancerogenic.py +2 -3
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthHumanToxicityNonCancerogenic.py +2 -3
- hestia_earth/models/lcImpactCertainEffects100Years/damageToHumanHealthStratosphericOzoneDepletion.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToMarineEcosystemsMarineEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactCertainEffects100Years/damageToMarineEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsClimateChange.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactCertainEffects100Years/damageToTerrestrialEcosystemsTerrestrialEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsFreshwaterEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToFreshwaterEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealth.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthClimateChange.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthHumanToxicityCancerogenic.py +2 -3
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthHumanToxicityNonCancerogenic.py +2 -3
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToHumanHealthStratosphericOzoneDepletion.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToMarineEcosystemsMarineEcotoxicity.py +2 -3
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToMarineEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsClimateChange.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsPdfYear.py +2 -2
- hestia_earth/models/lcImpactCertainEffectsInfinite/damageToTerrestrialEcosystemsTerrestrialEcotoxicity.py +2 -3
- hestia_earth/models/linkedImpactAssessment/emissions.py +3 -0
- hestia_earth/models/log.py +4 -3
- hestia_earth/models/mocking/search-results.json +575 -575
- hestia_earth/models/pooreNemecek2018/excretaKgN.py +4 -4
- hestia_earth/models/pooreNemecek2018/excretaKgVs.py +4 -4
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterCropResidueDecomposition.py +1 -1
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterExcreta.py +1 -1
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterInorganicFertiliser.py +1 -1
- hestia_earth/models/pooreNemecek2018/no3ToGroundwaterOrganicFertiliser.py +1 -1
- hestia_earth/models/pooreNemecek2018/{saplings.py → saplingsDepreciatedAmountPerCycle.py} +1 -1
- hestia_earth/models/pooreNemecek2018/utils.py +7 -1
- hestia_earth/models/recipe2016Egalitarian/damageToFreshwaterEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/damageToHumanHealth.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/damageToMarineEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/damageToTerrestrialEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/freshwaterAquaticEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Egalitarian/freshwaterEutrophicationPotential.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/humanCarcinogenicToxicity.py +2 -3
- hestia_earth/models/recipe2016Egalitarian/humanNonCarcinogenicToxicity.py +2 -3
- hestia_earth/models/recipe2016Egalitarian/marineAquaticEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Egalitarian/marineEutrophicationPotential.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/ozoneDepletionPotential.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/terrestrialAcidificationPotential.py +2 -2
- hestia_earth/models/recipe2016Egalitarian/terrestrialEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Hierarchist/damageToFreshwaterEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/damageToHumanHealth.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/damageToMarineEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/damageToTerrestrialEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/freshwaterAquaticEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Hierarchist/freshwaterEutrophicationPotential.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/humanCarcinogenicToxicity.py +2 -3
- hestia_earth/models/recipe2016Hierarchist/humanNonCarcinogenicToxicity.py +2 -3
- hestia_earth/models/recipe2016Hierarchist/marineAquaticEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Hierarchist/marineEutrophicationPotential.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/ozoneDepletionPotential.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/terrestrialAcidificationPotential.py +2 -2
- hestia_earth/models/recipe2016Hierarchist/terrestrialEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Individualist/damageToFreshwaterEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Individualist/damageToHumanHealth.py +2 -2
- hestia_earth/models/recipe2016Individualist/damageToMarineEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Individualist/damageToTerrestrialEcosystemsSpeciesYear.py +2 -2
- hestia_earth/models/recipe2016Individualist/freshwaterAquaticEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Individualist/freshwaterEutrophicationPotential.py +2 -2
- hestia_earth/models/recipe2016Individualist/humanCarcinogenicToxicity.py +2 -3
- hestia_earth/models/recipe2016Individualist/humanNonCarcinogenicToxicity.py +2 -3
- hestia_earth/models/recipe2016Individualist/marineAquaticEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/recipe2016Individualist/marineEutrophicationPotential.py +2 -2
- hestia_earth/models/recipe2016Individualist/ozoneDepletionPotential.py +2 -2
- hestia_earth/models/recipe2016Individualist/terrestrialAcidificationPotential.py +2 -2
- hestia_earth/models/recipe2016Individualist/terrestrialEcotoxicityPotential14Dcbeq.py +2 -3
- hestia_earth/models/site/management.py +142 -144
- hestia_earth/models/stehfestBouwman2006/n2OToAirCropResidueDecompositionDirect.py +1 -1
- hestia_earth/models/stehfestBouwman2006/n2OToAirExcretaDirect.py +1 -1
- hestia_earth/models/stehfestBouwman2006/n2OToAirInorganicFertiliserDirect.py +1 -1
- hestia_earth/models/stehfestBouwman2006/n2OToAirOrganicFertiliserDirect.py +1 -1
- hestia_earth/models/stehfestBouwman2006/noxToAirCropResidueDecomposition.py +1 -1
- hestia_earth/models/stehfestBouwman2006/noxToAirExcreta.py +1 -1
- hestia_earth/models/stehfestBouwman2006/noxToAirInorganicFertiliser.py +1 -1
- hestia_earth/models/stehfestBouwman2006/noxToAirOrganicFertiliser.py +1 -1
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirCropResidueDecomposition.py +1 -1
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirExcreta.py +1 -1
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirInorganicFertiliser.py +1 -1
- hestia_earth/models/stehfestBouwman2006GisImplementation/noxToAirOrganicFertiliser.py +1 -1
- hestia_earth/models/usetoxV2/freshwaterEcotoxicityPotentialCtue.py +2 -3
- hestia_earth/models/utils/__init__.py +4 -1
- hestia_earth/models/utils/blank_node.py +34 -14
- hestia_earth/models/utils/emission.py +1 -8
- hestia_earth/models/utils/lookup.py +2 -1
- hestia_earth/models/utils/management.py +11 -0
- hestia_earth/models/utils/pesticideAI.py +11 -17
- hestia_earth/models/utils/term.py +2 -1
- hestia_earth/models/version.py +1 -1
- {hestia_earth_models-0.64.11.dist-info → hestia_earth_models-0.64.13.dist-info}/METADATA +4 -4
- {hestia_earth_models-0.64.11.dist-info → hestia_earth_models-0.64.13.dist-info}/RECORD +192 -188
- {hestia_earth_models-0.64.11.dist-info → hestia_earth_models-0.64.13.dist-info}/WHEEL +1 -1
- tests/models/cycle/completeness/test_seed.py +1 -1
- tests/models/cycle/test_endDate.py +18 -2
- tests/models/cycle/test_startDate.py +21 -3
- tests/models/faostat2018/test_faostat_utils.py +84 -0
- tests/models/hestia/__init__.py +0 -0
- tests/models/hestia/test_landCover.py +210 -0
- tests/models/ipcc2019/animal/test_milkYieldPerAnimal.py +21 -0
- tests/models/ipcc2019/test_aboveGroundBiomass.py +2 -1
- tests/models/ipcc2019/test_belowGroundBiomass.py +2 -1
- tests/models/ipcc2019/test_co2ToAirAboveGroundBiomassStockChange.py +4 -3
- tests/models/ipcc2019/test_co2ToAirBelowGroundBiomassStockChange.py +3 -3
- tests/models/ipcc2019/test_co2ToAirSoilOrganicCarbonStockChange.py +49 -2
- tests/models/ipcc2019/test_n2OToAirExcretaDirect.py +12 -0
- tests/models/ipcc2019/test_organicCarbonPerHa.py +1 -0
- tests/models/koble2014/test_aboveGroundCropResidue.py +13 -0
- tests/models/pooreNemecek2018/test_no3ToGroundwaterCropResidueDecomposition.py +3 -2
- tests/models/pooreNemecek2018/test_no3ToGroundwaterExcreta.py +3 -2
- tests/models/pooreNemecek2018/test_no3ToGroundwaterInorganicFertiliser.py +3 -2
- tests/models/pooreNemecek2018/test_no3ToGroundwaterOrganicFertiliser.py +3 -2
- tests/models/pooreNemecek2018/{test_saplings.py → test_saplingsDepreciatedAmountPerCycle.py} +1 -1
- tests/models/site/test_management.py +18 -151
- tests/models/utils/test_blank_node.py +57 -1
- tests/models/utils/test_emission.py +1 -6
- tests/models/utils/test_site.py +33 -2
- tests/models/pooreNemecek2018/test_no3ToGroundwaterSoilFlux.py +0 -90
- tests/models/stehfestBouwman2006/test_n2OToAirSoilFlux.py +0 -41
- tests/models/stehfestBouwman2006/test_noxToAirSoilFlux.py +0 -40
- tests/models/stehfestBouwman2006GisImplementation/test_noxToAirSoilFlux.py +0 -33
- /hestia_earth/models/pooreNemecek2018/{no3ToGroundwaterSoilFlux.py → no3ToGroundwaterSoilFlux_utils.py} +0 -0
- /hestia_earth/models/stehfestBouwman2006/{n2OToAirSoilFlux.py → n2OToAirSoilFlux_utils.py} +0 -0
- /hestia_earth/models/stehfestBouwman2006/{noxToAirSoilFlux.py → noxToAirSoilFlux_utils.py} +0 -0
- /hestia_earth/models/stehfestBouwman2006GisImplementation/{noxToAirSoilFlux.py → noxToAirSoilFlux_utils.py} +0 -0
- {hestia_earth_models-0.64.11.dist-info → hestia_earth_models-0.64.13.dist-info}/LICENSE +0 -0
- {hestia_earth_models-0.64.11.dist-info → hestia_earth_models-0.64.13.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Land Cover
|
|
3
|
+
|
|
4
|
+
This model calculates historic land use change over a twenty-year period, extending the
|
|
5
|
+
functionality of the Blonk model.
|
|
6
|
+
"""
|
|
7
|
+
import math
|
|
8
|
+
from collections import defaultdict
|
|
9
|
+
from hestia_earth.schema import SiteSiteType, TermTermType
|
|
10
|
+
from hestia_earth.utils.lookup import (
|
|
11
|
+
download_lookup, get_table_value, column_name,
|
|
12
|
+
extract_grouped_data_closest_date, _is_missing_value, extract_grouped_data
|
|
13
|
+
)
|
|
14
|
+
from hestia_earth.utils.model import filter_list_term_type
|
|
15
|
+
from hestia_earth.utils.tools import safe_parse_float, to_precision, non_empty_value
|
|
16
|
+
|
|
17
|
+
from hestia_earth.models.log import logRequirements, log_as_table, logShouldRun
|
|
18
|
+
from hestia_earth.models.utils.management import _new_management
|
|
19
|
+
from hestia_earth.models.utils.term import get_lookup_value
|
|
20
|
+
from . import MODEL
|
|
21
|
+
|
|
22
|
+
REQUIREMENTS = {
|
|
23
|
+
"Site": {
|
|
24
|
+
"siteType": [
|
|
25
|
+
"forest",
|
|
26
|
+
"cropland",
|
|
27
|
+
"permanent pasture",
|
|
28
|
+
"other natural vegetation"
|
|
29
|
+
],
|
|
30
|
+
"country": "",
|
|
31
|
+
"management": [
|
|
32
|
+
{
|
|
33
|
+
"@type": "Management",
|
|
34
|
+
"value": "",
|
|
35
|
+
"term.termType": "landCover",
|
|
36
|
+
"endDate": ""
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
RETURNS = {
|
|
42
|
+
"Management": [{
|
|
43
|
+
"@type": "Management",
|
|
44
|
+
"term.termType": "landCover",
|
|
45
|
+
"term.@id": [
|
|
46
|
+
"Forest", "Annual cropland", "Permanent cropland", "Permanent pasture", "Other natural vegetation"
|
|
47
|
+
],
|
|
48
|
+
"value": "",
|
|
49
|
+
"endDate": "",
|
|
50
|
+
"startDate": ""
|
|
51
|
+
}]
|
|
52
|
+
}
|
|
53
|
+
LOOKUPS = {
|
|
54
|
+
"region-crop-cropGroupingFaostatProduction-areaHarvestedUpTo20YearExpansion": "All crops",
|
|
55
|
+
"region-crop-cropGroupingFaostatProduction-areaHarvested": "All crops",
|
|
56
|
+
"region-faostatArea-UpTo20YearExpansion": "All land uses",
|
|
57
|
+
"region-faostatArea": [
|
|
58
|
+
"Arable land",
|
|
59
|
+
"Cropland",
|
|
60
|
+
"Forest land",
|
|
61
|
+
"Land area",
|
|
62
|
+
"Other land",
|
|
63
|
+
"Permanent crops",
|
|
64
|
+
"Permanent meadows and pastures"
|
|
65
|
+
],
|
|
66
|
+
"crop": ["cropGroupingFaostatArea", "IPCC_LAND_USE_CATEGORY"],
|
|
67
|
+
"landCover": ["cropGroupingFaostatProduction", "FAOSTAT_LAND_AREA_CATEGORY"]
|
|
68
|
+
}
|
|
69
|
+
MODEL_KEY = 'landCover'
|
|
70
|
+
|
|
71
|
+
LAND_AREA = LOOKUPS["region-faostatArea"][3]
|
|
72
|
+
TOTAL_CROPLAND = "Cropland"
|
|
73
|
+
ANNUAL_CROPLAND = "Arable land"
|
|
74
|
+
FOREST_LAND = "Forest land"
|
|
75
|
+
OTHER_LAND = "Other land"
|
|
76
|
+
PERMANENT_CROPLAND = "Permanent crops"
|
|
77
|
+
PERMANENT_PASTURE = "Permanent meadows and pastures"
|
|
78
|
+
TOTAL_AGRICULTURAL_CHANGE = "Total agricultural change"
|
|
79
|
+
ALL_LAND_USE_TERMS = [
|
|
80
|
+
FOREST_LAND,
|
|
81
|
+
TOTAL_CROPLAND,
|
|
82
|
+
ANNUAL_CROPLAND,
|
|
83
|
+
PERMANENT_CROPLAND,
|
|
84
|
+
PERMANENT_PASTURE,
|
|
85
|
+
OTHER_LAND
|
|
86
|
+
]
|
|
87
|
+
# Mapping from Land use terms to Management node terms.
|
|
88
|
+
# land use term: (@id, name)
|
|
89
|
+
LAND_USE_TERMS_FOR_TRANSFORMATION = {
|
|
90
|
+
FOREST_LAND: ("forest", "Forest"),
|
|
91
|
+
ANNUAL_CROPLAND: ("annualCropland", "Annual cropland"),
|
|
92
|
+
PERMANENT_CROPLAND: ("permanentCropland", "Permanent cropland"),
|
|
93
|
+
PERMANENT_PASTURE: ("permanentPasture", "Permanent pasture"),
|
|
94
|
+
OTHER_LAND: ("otherLand", OTHER_LAND) # Not used yet
|
|
95
|
+
}
|
|
96
|
+
SITE_TYPES = {
|
|
97
|
+
SiteSiteType.CROPLAND.value,
|
|
98
|
+
SiteSiteType.FOREST.value,
|
|
99
|
+
SiteSiteType.OTHER_NATURAL_VEGETATION.value,
|
|
100
|
+
SiteSiteType.PERMANENT_PASTURE.value
|
|
101
|
+
}
|
|
102
|
+
DEFAULT_WINDOW_IN_YEARS = 20
|
|
103
|
+
IPCC_LAND_USE_CATEGORY_ANNUAL = "Annual crops"
|
|
104
|
+
IPCC_LAND_USE_CATEGORY_PERENNIAL = "Perennial crops"
|
|
105
|
+
OUTPUT_SIGNIFICANT_DIGITS = 3
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _management(term_id: str, value: float, start_date: str, end_date: str):
|
|
109
|
+
node = _new_management(term_id, MODEL)
|
|
110
|
+
node['value'] = value
|
|
111
|
+
node['startDate'] = start_date
|
|
112
|
+
node['endDate'] = end_date
|
|
113
|
+
return node
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _is_missing_or_none(value) -> bool:
|
|
117
|
+
return value is None or _is_missing_value(value)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _safe_divide(numerator, denominator, default=0) -> float:
|
|
121
|
+
return default if not denominator else numerator / denominator
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def site_area_sum_to_100(dict_of_percentages: dict):
|
|
125
|
+
return False if dict_of_percentages == {} else \
|
|
126
|
+
(math.isclose(sum(dict_of_percentages.values()), 1.0, rel_tol=0.01) or
|
|
127
|
+
math.isclose(sum(dict_of_percentages.values()), 0.0, rel_tol=0.01))
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _lookup_land_use_type(nodes: list) -> str:
|
|
131
|
+
"""Look up the land use type from a management node."""
|
|
132
|
+
return "" if nodes == [] else get_lookup_value(
|
|
133
|
+
lookup_term=nodes[0].get("term", {}),
|
|
134
|
+
column=LOOKUPS.get("landCover")[1],
|
|
135
|
+
model=MODEL,
|
|
136
|
+
term=nodes[0].get("term", {})
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _crop_ipcc_land_use_category(
|
|
141
|
+
crop_term_id: str,
|
|
142
|
+
lookup_term_type: str = TermTermType.LANDCOVER.value
|
|
143
|
+
) -> str:
|
|
144
|
+
"""
|
|
145
|
+
Looks up the crop in the lookup.
|
|
146
|
+
Returns the IPCC_LAND_USE_CATEGORY.
|
|
147
|
+
"""
|
|
148
|
+
return get_lookup_value(
|
|
149
|
+
lookup_term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type},
|
|
150
|
+
column=LOOKUPS.get("crop")[1],
|
|
151
|
+
model=MODEL,
|
|
152
|
+
term={"@id": crop_term_id, "type": "Term", "termType": lookup_term_type}
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def get_changes(country_id: str, end_year: int) -> dict:
|
|
157
|
+
"""
|
|
158
|
+
For each entry in ALL_LAND_USE_TERMS, creates a key: value in output dictionary, also TOTAL
|
|
159
|
+
"""
|
|
160
|
+
lookup = download_lookup("region-faostatArea-UpTo20YearExpansion.csv")
|
|
161
|
+
changes_dict = {
|
|
162
|
+
land_use_term: safe_parse_float(
|
|
163
|
+
extract_grouped_data(
|
|
164
|
+
get_table_value(lookup, 'termid', country_id, column_name(land_use_term)),
|
|
165
|
+
str(end_year))
|
|
166
|
+
)
|
|
167
|
+
for land_use_term in ALL_LAND_USE_TERMS + [LAND_AREA]
|
|
168
|
+
}
|
|
169
|
+
changes_dict[TOTAL_AGRICULTURAL_CHANGE] = (float(changes_dict.get(TOTAL_CROPLAND, 0))
|
|
170
|
+
+ float(changes_dict.get(PERMANENT_PASTURE, 0)))
|
|
171
|
+
|
|
172
|
+
return changes_dict
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _get_ratio_start_and_end_values(
|
|
176
|
+
expansion: float,
|
|
177
|
+
fao_name: str,
|
|
178
|
+
country_id: str,
|
|
179
|
+
end_year: int
|
|
180
|
+
) -> float:
|
|
181
|
+
# expansion over twenty years / current area
|
|
182
|
+
lookup = download_lookup('region-faostatArea.csv')
|
|
183
|
+
table_value = get_table_value(lookup, 'termid', country_id, column_name(fao_name))
|
|
184
|
+
end_value = safe_parse_float(value=extract_grouped_data_closest_date(table_value, end_year), default=None)
|
|
185
|
+
return max(0.0, _safe_divide(numerator=expansion, denominator=end_value))
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def _estimate_maximum_forest_change(
|
|
189
|
+
forest_change: float, total_cropland_change: float, pasture_change: float, total_agricultural_change: float
|
|
190
|
+
):
|
|
191
|
+
"""
|
|
192
|
+
(L): Estimate maximum forest loss
|
|
193
|
+
Gives a negative number representing forest loss. Does not currently handle forest gain.
|
|
194
|
+
"""
|
|
195
|
+
positive_change = pasture_change > 0 and total_cropland_change > 0
|
|
196
|
+
return _negative_agricultural_land_change(
|
|
197
|
+
forest_change=forest_change,
|
|
198
|
+
pasture_change=pasture_change,
|
|
199
|
+
total_cropland_change=total_cropland_change
|
|
200
|
+
) if not positive_change else (
|
|
201
|
+
total_agricultural_change
|
|
202
|
+
if -min(forest_change, 0) > total_agricultural_change else
|
|
203
|
+
min(forest_change, 0)
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def _negative_agricultural_land_change(forest_change, pasture_change, total_cropland_change):
|
|
208
|
+
return pasture_change if 0 < pasture_change < -min(forest_change, 0) \
|
|
209
|
+
else min(forest_change, 0) if pasture_change > 0 \
|
|
210
|
+
else -total_cropland_change if 0 < total_cropland_change < -min(forest_change, 0) \
|
|
211
|
+
else min(forest_change, 0) if 0 < total_cropland_change \
|
|
212
|
+
else 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _allocate_forest_loss(forest_loss: float, changes: dict):
|
|
216
|
+
"""Allocate forest loss between agricultural categories for the specific country"""
|
|
217
|
+
return {
|
|
218
|
+
TOTAL_CROPLAND: forest_loss * _safe_divide(
|
|
219
|
+
numerator=max(changes[TOTAL_CROPLAND], 0),
|
|
220
|
+
denominator=max(changes[TOTAL_CROPLAND], 0) + max(changes[PERMANENT_PASTURE], 0)
|
|
221
|
+
),
|
|
222
|
+
PERMANENT_PASTURE: forest_loss * _safe_divide(
|
|
223
|
+
numerator=max(changes[PERMANENT_PASTURE], 0),
|
|
224
|
+
denominator=max(changes[TOTAL_CROPLAND], 0) + max(changes[PERMANENT_PASTURE], 0)
|
|
225
|
+
)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _additional_allocation(changes, max_forest_loss_to_cropland, max_forest_loss_to_permanent_pasture):
|
|
230
|
+
"""Determine how much area still needs to be assigned"""
|
|
231
|
+
return {
|
|
232
|
+
TOTAL_CROPLAND: max(changes[TOTAL_CROPLAND], 0) + max_forest_loss_to_cropland,
|
|
233
|
+
PERMANENT_PASTURE: max(changes[PERMANENT_PASTURE], 0) + max_forest_loss_to_permanent_pasture
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _allocate_cropland_loss_to_pasture(changes: dict, land_required_for_permanent_pasture: float):
|
|
238
|
+
"""Allocate changes between Permanent pasture and cropland"""
|
|
239
|
+
return (
|
|
240
|
+
max(-land_required_for_permanent_pasture, changes[TOTAL_CROPLAND])
|
|
241
|
+
if changes[TOTAL_CROPLAND] < 0 else 0
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def _allocate_pasture_loss_to_cropland(changes: dict, land_required_for_cropland: float):
|
|
246
|
+
"""Allocate changes between Permanent pasture and cropland"""
|
|
247
|
+
return (
|
|
248
|
+
max(-land_required_for_cropland, changes[PERMANENT_PASTURE])
|
|
249
|
+
if changes[PERMANENT_PASTURE] < 0 else 0
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def _allocate_other_land(
|
|
254
|
+
changes: dict, max_forest_loss_to: dict, pasture_loss_to_cropland: float, cropland_loss_to_pasture: float
|
|
255
|
+
) -> dict:
|
|
256
|
+
"""Allocate changes between Other land and cropland"""
|
|
257
|
+
other_land_loss_to_cropland = (
|
|
258
|
+
-(max(changes[TOTAL_CROPLAND], 0) + max_forest_loss_to[TOTAL_CROPLAND]
|
|
259
|
+
+ pasture_loss_to_cropland)
|
|
260
|
+
)
|
|
261
|
+
other_land_loss_to_pasture = (
|
|
262
|
+
-(max(changes[PERMANENT_PASTURE], 0) + max_forest_loss_to[PERMANENT_PASTURE]
|
|
263
|
+
+ cropland_loss_to_pasture)
|
|
264
|
+
)
|
|
265
|
+
return {
|
|
266
|
+
TOTAL_CROPLAND: other_land_loss_to_cropland,
|
|
267
|
+
PERMANENT_PASTURE: other_land_loss_to_pasture,
|
|
268
|
+
TOTAL_AGRICULTURAL_CHANGE: other_land_loss_to_cropland + other_land_loss_to_pasture
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def _allocate_annual_permanent_cropland_losses(changes: dict) -> tuple:
|
|
273
|
+
"""
|
|
274
|
+
(Z, AA): Allocate changes between Annual cropland and Permanent cropland
|
|
275
|
+
Returns: annual_cropland_loss_to_permanent_cropland, permanent_cropland_loss_to_annual_cropland
|
|
276
|
+
"""
|
|
277
|
+
return (
|
|
278
|
+
-min(-changes[ANNUAL_CROPLAND], changes[PERMANENT_CROPLAND])
|
|
279
|
+
if (changes[ANNUAL_CROPLAND] < 0 and changes[PERMANENT_CROPLAND] > 0) else 0,
|
|
280
|
+
-min(changes[ANNUAL_CROPLAND], -changes[PERMANENT_CROPLAND])
|
|
281
|
+
if (changes[ANNUAL_CROPLAND] > 0 and changes[PERMANENT_CROPLAND] < 0) else 0
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def _estimate_conversions_to_annual_cropland(
|
|
286
|
+
changes: dict,
|
|
287
|
+
pasture_loss_to_crops: float,
|
|
288
|
+
forest_loss_to_cropland: float,
|
|
289
|
+
other_land_loss_to_annual_cropland: float,
|
|
290
|
+
permanent_to_annual_cropland: float
|
|
291
|
+
) -> dict:
|
|
292
|
+
"""(AC-AG): Estimate percentage of land sources when converted to: Annual cropland"""
|
|
293
|
+
# -> percent_annual_cropland_was[]
|
|
294
|
+
def conversion_to_annual_cropland(factor: float):
|
|
295
|
+
return factor * _safe_divide(
|
|
296
|
+
numerator=_safe_divide(
|
|
297
|
+
numerator=max(changes[ANNUAL_CROPLAND], 0),
|
|
298
|
+
denominator=max(changes[ANNUAL_CROPLAND], 0) + max(changes[PERMANENT_CROPLAND], 0)),
|
|
299
|
+
denominator=-changes[ANNUAL_CROPLAND]
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
percentages = {
|
|
303
|
+
FOREST_LAND: conversion_to_annual_cropland(forest_loss_to_cropland),
|
|
304
|
+
OTHER_LAND: conversion_to_annual_cropland(other_land_loss_to_annual_cropland),
|
|
305
|
+
PERMANENT_PASTURE: conversion_to_annual_cropland(pasture_loss_to_crops),
|
|
306
|
+
PERMANENT_CROPLAND: _safe_divide(numerator=permanent_to_annual_cropland, denominator=-changes[ANNUAL_CROPLAND])
|
|
307
|
+
}
|
|
308
|
+
return percentages
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _estimate_conversions_to_permanent_cropland(
|
|
312
|
+
changes: dict,
|
|
313
|
+
annual_loss_to_permanent_cropland: float,
|
|
314
|
+
pasture_loss_to_cropland: float,
|
|
315
|
+
forest_loss_to_cropland: float,
|
|
316
|
+
other_land_loss_to_annual_cropland: float
|
|
317
|
+
) -> dict:
|
|
318
|
+
"""Estimate percentage of land sources when converted to: Annual cropland"""
|
|
319
|
+
def conversion_to_permanent_cropland(factor: float):
|
|
320
|
+
return _safe_divide(
|
|
321
|
+
numerator=_safe_divide(
|
|
322
|
+
numerator=factor * max(changes[PERMANENT_CROPLAND], 0),
|
|
323
|
+
denominator=max(changes[ANNUAL_CROPLAND], 0) + max(changes[PERMANENT_CROPLAND], 0)),
|
|
324
|
+
denominator=-changes[PERMANENT_CROPLAND]
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
percentages = {
|
|
328
|
+
FOREST_LAND: conversion_to_permanent_cropland(forest_loss_to_cropland),
|
|
329
|
+
OTHER_LAND: conversion_to_permanent_cropland(other_land_loss_to_annual_cropland),
|
|
330
|
+
PERMANENT_PASTURE: conversion_to_permanent_cropland(pasture_loss_to_cropland),
|
|
331
|
+
ANNUAL_CROPLAND: conversion_to_permanent_cropland(annual_loss_to_permanent_cropland)
|
|
332
|
+
}
|
|
333
|
+
return percentages
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def _estimate_conversions_to_pasture(
|
|
337
|
+
changes: dict,
|
|
338
|
+
forest_loss_to_pasture: float,
|
|
339
|
+
total_cropland_loss_to_pasture: float,
|
|
340
|
+
other_land_loss_to_pasture: float
|
|
341
|
+
) -> dict:
|
|
342
|
+
"""Estimate percentage of land sources when converted to: Permanent pasture"""
|
|
343
|
+
percentages = {
|
|
344
|
+
FOREST_LAND: _safe_divide(
|
|
345
|
+
numerator=forest_loss_to_pasture,
|
|
346
|
+
denominator=-changes[PERMANENT_PASTURE],
|
|
347
|
+
),
|
|
348
|
+
OTHER_LAND: _safe_divide(
|
|
349
|
+
numerator=other_land_loss_to_pasture,
|
|
350
|
+
denominator=-changes[PERMANENT_PASTURE]
|
|
351
|
+
),
|
|
352
|
+
# AT
|
|
353
|
+
ANNUAL_CROPLAND: _safe_divide(
|
|
354
|
+
numerator=total_cropland_loss_to_pasture * _safe_divide(
|
|
355
|
+
numerator=min(changes[ANNUAL_CROPLAND], 0),
|
|
356
|
+
denominator=(min(changes[ANNUAL_CROPLAND], 0) + min(changes[PERMANENT_CROPLAND], 0))
|
|
357
|
+
),
|
|
358
|
+
denominator=-changes[PERMANENT_PASTURE]
|
|
359
|
+
),
|
|
360
|
+
PERMANENT_CROPLAND: _safe_divide(
|
|
361
|
+
numerator=total_cropland_loss_to_pasture * _safe_divide(
|
|
362
|
+
numerator=min(changes[PERMANENT_CROPLAND], 0),
|
|
363
|
+
denominator=(min(changes[ANNUAL_CROPLAND], 0) + min(changes[PERMANENT_CROPLAND], 0))
|
|
364
|
+
),
|
|
365
|
+
denominator=-changes[PERMANENT_PASTURE]
|
|
366
|
+
)
|
|
367
|
+
}
|
|
368
|
+
return percentages
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def _get_shares_of_expansion(
|
|
372
|
+
land_use_type: str,
|
|
373
|
+
percent_annual_cropland_was: dict,
|
|
374
|
+
percent_permanent_cropland_was: dict,
|
|
375
|
+
percent_pasture_was: dict
|
|
376
|
+
) -> dict:
|
|
377
|
+
expansion_for_type = {
|
|
378
|
+
ANNUAL_CROPLAND: percent_annual_cropland_was,
|
|
379
|
+
PERMANENT_CROPLAND: percent_permanent_cropland_was,
|
|
380
|
+
PERMANENT_PASTURE: percent_pasture_was
|
|
381
|
+
}
|
|
382
|
+
return {
|
|
383
|
+
k: expansion_for_type[land_use_type].get(k, 0)
|
|
384
|
+
for k in LAND_USE_TERMS_FOR_TRANSFORMATION.keys()
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _get_faostat_name(term: dict) -> str:
|
|
389
|
+
"""For landCover terms, find the cropGroupingFaostatArea name for the landCover id."""
|
|
390
|
+
return get_lookup_value(term, "cropGroupingFaostatArea")
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _get_complete_faostat_to_crop_mapping() -> dict:
|
|
394
|
+
"""Returns mapping in the format: {faostat_name: IPPC_LAND_USE_CATEGORY, ...}"""
|
|
395
|
+
lookup = download_lookup("crop.csv")
|
|
396
|
+
mappings = defaultdict(list)
|
|
397
|
+
for crop_term_id in [row[0] for row in lookup]:
|
|
398
|
+
key = column_name(
|
|
399
|
+
get_table_value(lookup, 'termid', crop_term_id, column_name("cropGroupingFaostatArea"))
|
|
400
|
+
)
|
|
401
|
+
if key:
|
|
402
|
+
mappings[key].append(_crop_ipcc_land_use_category(crop_term_id=crop_term_id, lookup_term_type="crop"))
|
|
403
|
+
return {
|
|
404
|
+
fao_name: max(set(crop_terms), key=crop_terms.count)
|
|
405
|
+
for fao_name, crop_terms in mappings.items()
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def _get_harvested_area(country_id: str, year: int, faostat_name: str) -> float:
|
|
410
|
+
"""
|
|
411
|
+
Returns a dictionary of harvested areas for the country & year, indexed by landCover term (crop)
|
|
412
|
+
"""
|
|
413
|
+
lookup = download_lookup("region-crop-cropGroupingFaostatProduction-areaHarvested.csv")
|
|
414
|
+
return safe_parse_float(
|
|
415
|
+
value=extract_grouped_data_closest_date(
|
|
416
|
+
data=get_table_value(lookup, "termid", country_id, column_name(faostat_name)),
|
|
417
|
+
year=year
|
|
418
|
+
),
|
|
419
|
+
default=None
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _run_make_management_nodes(existing_nodes: list, percentage_transformed_from: dict, start_year: int) -> list:
|
|
424
|
+
"""Creates a list of new management nodes, excluding any dates matching existing ones."""
|
|
425
|
+
existing_nodes_set = {
|
|
426
|
+
(node.get("term", {}).get("@id", ""), node.get("startDate"), node.get("endDate"))
|
|
427
|
+
for node in existing_nodes
|
|
428
|
+
}
|
|
429
|
+
values = [
|
|
430
|
+
{
|
|
431
|
+
"land_management_key": (
|
|
432
|
+
LAND_USE_TERMS_FOR_TRANSFORMATION[land_type], f"{start_year}-01-01", f"{start_year}-12-31"
|
|
433
|
+
),
|
|
434
|
+
"land_type": land_type,
|
|
435
|
+
"percentage": 0.0 if ratio == -0.0 else to_precision(
|
|
436
|
+
number=ratio * 100,
|
|
437
|
+
digits=OUTPUT_SIGNIFICANT_DIGITS
|
|
438
|
+
)
|
|
439
|
+
}
|
|
440
|
+
for land_type, ratio in percentage_transformed_from.items()
|
|
441
|
+
]
|
|
442
|
+
values = [v for v in values if v.get("land_management_key") not in existing_nodes_set]
|
|
443
|
+
|
|
444
|
+
return [
|
|
445
|
+
_management(
|
|
446
|
+
term_id=LAND_USE_TERMS_FOR_TRANSFORMATION[value.get("land_type")][0],
|
|
447
|
+
value=value.get("percentage"),
|
|
448
|
+
start_date=value.get("land_management_key")[1],
|
|
449
|
+
end_date=value.get("land_management_key")[2]
|
|
450
|
+
)
|
|
451
|
+
for value in values
|
|
452
|
+
]
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def get_ratio_of_expanded_area(country_id: str, fao_name: str, end_year: int) -> float:
|
|
456
|
+
lookup = download_lookup("region-crop-cropGroupingFaostatProduction-areaHarvestedUpTo20YearExpansion.csv")
|
|
457
|
+
table_value = get_table_value(lookup, 'termid', country_id, column_name(fao_name))
|
|
458
|
+
expansion = safe_parse_float(value=extract_grouped_data(table_value, str(end_year)), default=None)
|
|
459
|
+
end_value = _get_harvested_area(
|
|
460
|
+
country_id=country_id,
|
|
461
|
+
year=end_year,
|
|
462
|
+
faostat_name=fao_name
|
|
463
|
+
)
|
|
464
|
+
return 0.0 if any([expansion is None, end_value is None]) else max(
|
|
465
|
+
0.0, _safe_divide(numerator=expansion, denominator=(end_value - expansion))
|
|
466
|
+
)
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
def _get_sum_for_land_category(
|
|
470
|
+
values: dict,
|
|
471
|
+
year: int,
|
|
472
|
+
ipcc_land_use_category,
|
|
473
|
+
fao_stat_to_ipcc_type: dict,
|
|
474
|
+
include_negatives: bool = True
|
|
475
|
+
) -> float:
|
|
476
|
+
return sum(
|
|
477
|
+
[
|
|
478
|
+
safe_parse_float(value=extract_grouped_data(table_value, str(year)), default=None)
|
|
479
|
+
for fao_name, table_value in values.items()
|
|
480
|
+
if not _is_missing_or_none(extract_grouped_data(table_value, str(year))) and
|
|
481
|
+
fao_stat_to_ipcc_type[fao_name] == ipcc_land_use_category and
|
|
482
|
+
(include_negatives or
|
|
483
|
+
safe_parse_float(value=extract_grouped_data(table_value, str(year)), default=None) > 0.0)
|
|
484
|
+
]
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def _get_sums_of_crop_expansion(country_id: str, year: int, include_negatives: bool = True) -> tuple[float, float]:
|
|
489
|
+
"""
|
|
490
|
+
Sum net expansion for all annual and permanent crops, returned as two values.
|
|
491
|
+
Returns a tuple of (expansion of annual crops, expansion of permanent crops)
|
|
492
|
+
"""
|
|
493
|
+
lookup = download_lookup("region-crop-cropGroupingFaostatProduction-areaHarvestedUpTo20YearExpansion.csv")
|
|
494
|
+
values = {name: get_table_value(lookup, 'termid', country_id, column_name(name))
|
|
495
|
+
for name in list(lookup.dtype.names) if name != "termid"}
|
|
496
|
+
|
|
497
|
+
fao_stat_to_ipcc_type = _get_complete_faostat_to_crop_mapping()
|
|
498
|
+
|
|
499
|
+
annual_sum_of_expansion = _get_sum_for_land_category(
|
|
500
|
+
values=values,
|
|
501
|
+
year=year,
|
|
502
|
+
ipcc_land_use_category=IPCC_LAND_USE_CATEGORY_ANNUAL,
|
|
503
|
+
fao_stat_to_ipcc_type=fao_stat_to_ipcc_type,
|
|
504
|
+
include_negatives=include_negatives
|
|
505
|
+
)
|
|
506
|
+
permanent_sum_of_expansion = _get_sum_for_land_category(
|
|
507
|
+
values=values,
|
|
508
|
+
year=year,
|
|
509
|
+
ipcc_land_use_category=IPCC_LAND_USE_CATEGORY_PERENNIAL,
|
|
510
|
+
fao_stat_to_ipcc_type=fao_stat_to_ipcc_type,
|
|
511
|
+
include_negatives=include_negatives
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
return annual_sum_of_expansion, permanent_sum_of_expansion
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _get_net_expansion_cultivated_vs_harvested(annual_crops_net_expansion, changes, land_use_type,
|
|
518
|
+
permanent_crops_net_expansion):
|
|
519
|
+
if land_use_type == ANNUAL_CROPLAND:
|
|
520
|
+
net_expansion_cultivated_vs_harvested = _safe_divide(numerator=max(0, changes[ANNUAL_CROPLAND]),
|
|
521
|
+
denominator=(annual_crops_net_expansion / 1000))
|
|
522
|
+
elif land_use_type == PERMANENT_CROPLAND:
|
|
523
|
+
net_expansion_cultivated_vs_harvested = _safe_divide(numerator=max(0, changes[PERMANENT_CROPLAND]),
|
|
524
|
+
denominator=(permanent_crops_net_expansion / 1000))
|
|
525
|
+
else:
|
|
526
|
+
net_expansion_cultivated_vs_harvested = 1
|
|
527
|
+
return net_expansion_cultivated_vs_harvested
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def _should_run_historical_land_use_change(site: dict, land_use_type: str) -> tuple[bool, dict]:
|
|
531
|
+
management_nodes = filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
|
|
532
|
+
# Assume a single management node for single-cropping.
|
|
533
|
+
return _should_run_historical_land_use_change_single_crop(
|
|
534
|
+
site=site,
|
|
535
|
+
term=management_nodes[0].get("term", {}),
|
|
536
|
+
country_id=site.get("country", {}).get("@id"),
|
|
537
|
+
end_year=int(management_nodes[0].get("endDate")[:4]),
|
|
538
|
+
land_use_type=land_use_type
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
def _should_run_historical_land_use_change_single_crop(
|
|
543
|
+
site: dict,
|
|
544
|
+
term: dict,
|
|
545
|
+
country_id: str,
|
|
546
|
+
end_year: int,
|
|
547
|
+
land_use_type: str
|
|
548
|
+
) -> tuple[bool, dict]:
|
|
549
|
+
"""Calculate land use change percentages for a single management node/crop."""
|
|
550
|
+
# (C-H).
|
|
551
|
+
changes = get_changes(country_id=country_id, end_year=end_year)
|
|
552
|
+
|
|
553
|
+
# (L). Estimate maximum forest loss
|
|
554
|
+
forest_loss = _estimate_maximum_forest_change(
|
|
555
|
+
forest_change=changes[FOREST_LAND],
|
|
556
|
+
total_cropland_change=changes[TOTAL_CROPLAND],
|
|
557
|
+
pasture_change=changes[PERMANENT_PASTURE],
|
|
558
|
+
total_agricultural_change=changes[TOTAL_AGRICULTURAL_CHANGE]
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
# (M, N). Allocate forest loss between agricultural categories for the specific country
|
|
562
|
+
forest_loss_to = _allocate_forest_loss(forest_loss=forest_loss, changes=changes)
|
|
563
|
+
|
|
564
|
+
# (P, Q): Determine how much area still needs to be assigned
|
|
565
|
+
land_required_for = _additional_allocation(
|
|
566
|
+
changes=changes,
|
|
567
|
+
max_forest_loss_to_cropland=forest_loss_to[TOTAL_CROPLAND],
|
|
568
|
+
max_forest_loss_to_permanent_pasture=forest_loss_to[PERMANENT_PASTURE]
|
|
569
|
+
)
|
|
570
|
+
|
|
571
|
+
# (R): Allocate changes between Permanent pasture and cropland
|
|
572
|
+
cropland_loss_to_pasture = _allocate_cropland_loss_to_pasture(
|
|
573
|
+
changes=changes,
|
|
574
|
+
land_required_for_permanent_pasture=land_required_for[PERMANENT_PASTURE]
|
|
575
|
+
)
|
|
576
|
+
# (S)
|
|
577
|
+
pasture_loss_to_cropland = _allocate_pasture_loss_to_cropland(
|
|
578
|
+
changes=changes,
|
|
579
|
+
land_required_for_cropland=land_required_for[TOTAL_CROPLAND]
|
|
580
|
+
)
|
|
581
|
+
|
|
582
|
+
# (V): Allocate changes between Other land and cropland
|
|
583
|
+
other_land_loss_to = _allocate_other_land(
|
|
584
|
+
changes=changes,
|
|
585
|
+
max_forest_loss_to=forest_loss_to,
|
|
586
|
+
pasture_loss_to_cropland=pasture_loss_to_cropland,
|
|
587
|
+
cropland_loss_to_pasture=cropland_loss_to_pasture
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
# (Z, AA): Allocate changes between Annual cropland and Permanent cropland
|
|
591
|
+
annual_cropland_loss_to_permanent_cropland, permanent_cropland_loss_to_annual_cropland = (
|
|
592
|
+
_allocate_annual_permanent_cropland_losses(changes)
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
# (AC-AG): Estimate percentage of land sources when converted to: Annual cropland
|
|
596
|
+
# Note: All percentages are expressed as decimal fractions. 50% = 0.5
|
|
597
|
+
percent_annual_cropland_was = _estimate_conversions_to_annual_cropland(
|
|
598
|
+
changes=changes,
|
|
599
|
+
pasture_loss_to_crops=pasture_loss_to_cropland,
|
|
600
|
+
forest_loss_to_cropland=forest_loss_to[TOTAL_CROPLAND],
|
|
601
|
+
other_land_loss_to_annual_cropland=other_land_loss_to[TOTAL_CROPLAND],
|
|
602
|
+
permanent_to_annual_cropland=permanent_cropland_loss_to_annual_cropland,
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# (AJ-AM): Estimate percentage of land sources when converted to: Permanent cropland
|
|
606
|
+
percent_permanent_cropland_was = _estimate_conversions_to_permanent_cropland(
|
|
607
|
+
changes=changes,
|
|
608
|
+
annual_loss_to_permanent_cropland=annual_cropland_loss_to_permanent_cropland,
|
|
609
|
+
pasture_loss_to_cropland=pasture_loss_to_cropland,
|
|
610
|
+
forest_loss_to_cropland=forest_loss_to[TOTAL_CROPLAND],
|
|
611
|
+
other_land_loss_to_annual_cropland=other_land_loss_to[TOTAL_CROPLAND]
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
# Estimate percentage of land sources when converted to: Permanent pasture
|
|
615
|
+
percent_pasture_was = _estimate_conversions_to_pasture(
|
|
616
|
+
changes=changes,
|
|
617
|
+
forest_loss_to_pasture=forest_loss_to[PERMANENT_PASTURE],
|
|
618
|
+
total_cropland_loss_to_pasture=cropland_loss_to_pasture,
|
|
619
|
+
other_land_loss_to_pasture=other_land_loss_to[PERMANENT_PASTURE]
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
# BA to BD
|
|
623
|
+
shares_of_expansion = _get_shares_of_expansion(
|
|
624
|
+
land_use_type=land_use_type,
|
|
625
|
+
percent_annual_cropland_was=percent_annual_cropland_was,
|
|
626
|
+
percent_permanent_cropland_was=percent_permanent_cropland_was,
|
|
627
|
+
percent_pasture_was=percent_pasture_was
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
# Cell E8
|
|
631
|
+
expansion_factor = _get_ratio_start_and_end_values(
|
|
632
|
+
expansion=changes[PERMANENT_PASTURE],
|
|
633
|
+
fao_name=PERMANENT_PASTURE,
|
|
634
|
+
country_id=country_id,
|
|
635
|
+
end_year=end_year
|
|
636
|
+
) if land_use_type == PERMANENT_PASTURE else get_ratio_of_expanded_area(
|
|
637
|
+
country_id=country_id,
|
|
638
|
+
fao_name=_get_faostat_name(term),
|
|
639
|
+
end_year=end_year
|
|
640
|
+
)
|
|
641
|
+
|
|
642
|
+
# E9
|
|
643
|
+
annual_crops_net_expansion, permanent_crops_net_expansion = _get_sums_of_crop_expansion(
|
|
644
|
+
country_id=country_id,
|
|
645
|
+
year=end_year,
|
|
646
|
+
include_negatives=True
|
|
647
|
+
)
|
|
648
|
+
annual_crops_gross_expansion, permanent_crops_gross_expansion = _get_sums_of_crop_expansion(
|
|
649
|
+
country_id=country_id,
|
|
650
|
+
year=end_year,
|
|
651
|
+
include_negatives=False
|
|
652
|
+
)
|
|
653
|
+
e9_net_expansion = _safe_divide(
|
|
654
|
+
numerator=permanent_crops_net_expansion,
|
|
655
|
+
denominator=permanent_crops_gross_expansion
|
|
656
|
+
) if land_use_type == PERMANENT_CROPLAND else (
|
|
657
|
+
_safe_divide(
|
|
658
|
+
numerator=annual_crops_net_expansion,
|
|
659
|
+
denominator=annual_crops_gross_expansion
|
|
660
|
+
) if land_use_type == ANNUAL_CROPLAND else 1
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
# E10: Compare changes to annual/perennial cropland from net expansion.
|
|
664
|
+
net_expansion_cultivated_vs_harvested = _get_net_expansion_cultivated_vs_harvested(
|
|
665
|
+
annual_crops_net_expansion=annual_crops_net_expansion,
|
|
666
|
+
changes=changes,
|
|
667
|
+
land_use_type=land_use_type,
|
|
668
|
+
permanent_crops_net_expansion=permanent_crops_net_expansion
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
site_area = {
|
|
672
|
+
land_type: (
|
|
673
|
+
shares_of_expansion[land_type] * expansion_factor * e9_net_expansion * net_expansion_cultivated_vs_harvested
|
|
674
|
+
)
|
|
675
|
+
for land_type in LAND_USE_TERMS_FOR_TRANSFORMATION.keys()
|
|
676
|
+
if land_type != land_use_type
|
|
677
|
+
}
|
|
678
|
+
site_area[land_use_type] = 1 - sum(site_area.values())
|
|
679
|
+
|
|
680
|
+
sum_of_site_areas_is_100 = site_area_sum_to_100(site_area)
|
|
681
|
+
logRequirements(
|
|
682
|
+
log_node=site,
|
|
683
|
+
model=MODEL,
|
|
684
|
+
term=term.get("@id"),
|
|
685
|
+
model_key=MODEL_KEY,
|
|
686
|
+
land_use_type=land_use_type,
|
|
687
|
+
country_id=country_id,
|
|
688
|
+
site_area=log_as_table(site_area),
|
|
689
|
+
sum_of_site_areas_is_100=sum_of_site_areas_is_100
|
|
690
|
+
)
|
|
691
|
+
|
|
692
|
+
should_run = all(
|
|
693
|
+
[
|
|
694
|
+
site.get("siteType"),
|
|
695
|
+
country_id,
|
|
696
|
+
non_empty_value(term),
|
|
697
|
+
site.get("siteType") in SITE_TYPES,
|
|
698
|
+
sum_of_site_areas_is_100
|
|
699
|
+
]
|
|
700
|
+
)
|
|
701
|
+
logShouldRun(site, MODEL, term=term.get("@id"), should_run=should_run, key=MODEL_KEY)
|
|
702
|
+
|
|
703
|
+
return should_run, site_area
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _should_run(site: dict) -> tuple[bool, dict]:
|
|
707
|
+
management_nodes = filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
|
|
708
|
+
land_use_type = _lookup_land_use_type(nodes=management_nodes)
|
|
709
|
+
should_run_result, site_area = (
|
|
710
|
+
(False, {}) if land_use_type not in {ANNUAL_CROPLAND, PERMANENT_CROPLAND, PERMANENT_PASTURE}
|
|
711
|
+
else _should_run_historical_land_use_change(
|
|
712
|
+
site=site,
|
|
713
|
+
land_use_type=land_use_type
|
|
714
|
+
)
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
return should_run_result, site_area
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
def run(site: dict) -> list:
|
|
721
|
+
should_run, site_area = _should_run(site)
|
|
722
|
+
management_nodes = filter_list_term_type(site.get("management", []), TermTermType.LANDCOVER)
|
|
723
|
+
return _run_make_management_nodes(
|
|
724
|
+
existing_nodes=management_nodes,
|
|
725
|
+
percentage_transformed_from=site_area,
|
|
726
|
+
start_year=int(management_nodes[0].get("endDate")[:4]) - DEFAULT_WINDOW_IN_YEARS
|
|
727
|
+
) if should_run else []
|