Skip to content

Commit

Permalink
Merge pull request #215 from wri/arg_extra2
Browse files Browse the repository at this point in the history
Argentina government deforestation reporting (2nd update)
  • Loading branch information
danscales authored Feb 28, 2024
2 parents 7f701fb + 9be55b8 commit b404d97
Show file tree
Hide file tree
Showing 16 changed files with 421 additions and 76 deletions.
27 changes: 27 additions & 0 deletions src/main/scala/org/globalforestwatch/layers/Layer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,33 @@ case class ApproxYear(year: Int, approx: Boolean) extends Ordered[ApproxYear] {
def compare(that: ApproxYear): Int = Ordering.Tuple2[Int, Boolean].compare((this.year, this.approx), (that.year, that.approx))
}

object ApproxYear {
import _root_.io.circe.{KeyEncoder, KeyDecoder}

// Creating KeyEncoders for ApproxYear, so encoders for a map type with ApproxYear
// as key can happen. See https://circe.github.io/circe/codecs/custom-codecs.html.
implicit val approxYearKeyEncoder: KeyEncoder[ApproxYear] = new KeyEncoder[ApproxYear] {
override def apply(a: ApproxYear): String = {
if (a.approx) {
(-a.year).toString()
} else {
a.year.toString()
}
}
}

implicit val approxYearKeyDecoder: KeyDecoder[ApproxYear] = new KeyDecoder[ApproxYear] {
override def apply(key: String): Option[ApproxYear] = {
val n = key.toInt
if (n < 0) {
Some(ApproxYear(-n, true))
} else {
Some(ApproxYear(n, false))
}
}
}
}

trait ApproxYearLayer extends ILayer {
type B = ApproxYear

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,32 +117,14 @@ object ForestChangeDiagnosticAnalysis extends SummaryAnalysis {
.persist(StorageLevel.MEMORY_AND_DISK)
}

(cachedIntermediateResultsRDD match {
cachedIntermediateResultsRDD match {
case Some(cachedResults) =>
val mergedResults = partialResult.union(cachedResults)
saveIntermediateResults(mergedResults)
combineGridResults(mergedResults)
case None =>
combineGridResults(partialResult)
}).map(
// We don't want to mix country-specific forest loss for two countries. In
// the unusual case where location spans two countries and has forest loss in
// both countries, we convert the country code to "ERR" and zero out the
// country-specific forest loss.
(vl: ValidatedLocation[ForestChangeDiagnosticData]) => {
vl match {
case Valid(Location(id, dd)) => if (dd.country_code.value.size > 1) {
Valid(Location(id, dd.copy(country_code = ForestChangeDiagnosticDataDoubleCategory.fill("ERR", 0.0),
tree_cover_loss_country_specific_yearly = ForestChangeDiagnosticDataLossApproxYearly.empty,
tree_cover_loss_country_specific_wdpa_yearly = ForestChangeDiagnosticDataLossApproxYearly.empty,
tree_cover_loss_country_specific_primary_forest_yearly = ForestChangeDiagnosticDataLossApproxYearly.empty)))
} else {
vl
}
case Invalid(i) => vl
}
}
)
}
} catch {
case e: StackOverflowError =>
e.printStackTrace()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ object ForestChangeDiagnosticDF extends SummaryDF {
"tree_cover_loss_peat_yearly", //treeCoverLossPeatLandYearly
"tree_cover_loss_intact_forest_yearly", // treeCoverLossIntactForestYearly
"tree_cover_loss_protected_areas_yearly", // treeCoverLossProtectedAreasYearly
"tree_cover_loss_by_country_yearly",
"tree_cover_loss_by_country_wdpa_yearly",
"tree_cover_loss_by_country_landmark_yearly",
"tree_cover_loss_arg_otbn_yearly", // treeCoverLossARGOTBNYearly
"tree_cover_loss_sea_landcover_yearly", // treeCoverLossSEAsiaLandCoverYearly
"tree_cover_loss_idn_landcover_yearly", // treeCoverLossIDNLandCoverYearly
Expand All @@ -104,10 +107,10 @@ object ForestChangeDiagnosticDF extends SummaryDF {
"tree_cover_loss_prodes_yearly", // prodesLossYearly
"tree_cover_loss_prodes_wdpa_yearly", // prodesLossProtectedAreasYearly
"tree_cover_loss_prodes_primary_forest_yearly", // prodesLossProdesPrimaryForestYearly
"country_code", // countryCode
"tree_cover_loss_country_specific_yearly", // countrySpecificLossYearly
"tree_cover_loss_country_specific_wdpa_yearly", // countrySpecificLossProtectedAreasYearly
"tree_cover_loss_country_specific_primary_forest_yearly", // countrySpecificLossPrimaryForestYearly
"country_specific_deforestation_yearly",
"country_specific_deforestation_wdpa_yearly",
"country_specific_deforestation_landmark_yearly",
"country_specific_deforestation_classified_region_yearly",
"tree_cover_loss_brazil_biomes_yearly", // treeCoverLossBRABiomesYearly
"tree_cover_extent_total", // treeCoverExtent
"tree_cover_extent_primary_forest", // treeCoverExtentPrimaryForest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ case class ForestChangeDiagnosticData(
tree_cover_loss_peat_yearly: ForestChangeDiagnosticDataLossYearly,
tree_cover_loss_intact_forest_yearly: ForestChangeDiagnosticDataLossYearly,
tree_cover_loss_protected_areas_yearly: ForestChangeDiagnosticDataLossYearly,
// The "tree_cover_loss_by_country_*" fields are only filled in per-country when
// countryCode != "" (currently only ARG and BRA). These are for umdLoss
// comparison bars within the country-specific UI tabs.
tree_cover_loss_by_country_yearly: ForestChangeDiagnosticDataLossYearlyCategory,
tree_cover_loss_by_country_wdpa_yearly: ForestChangeDiagnosticDataLossYearlyTwoCategory,
tree_cover_loss_by_country_landmark_yearly: ForestChangeDiagnosticDataLossYearlyTwoCategory,
/** Tree cover loss in Argentina Native Forest Land Plan (OTBN) categories */
tree_cover_loss_arg_otbn_yearly: ForestChangeDiagnosticDataLossYearlyCategory,
/** Tree cover loss in south east asia */
Expand All @@ -31,10 +37,15 @@ case class ForestChangeDiagnosticData(
/** prodesLossProtectedAreasYearly */
tree_cover_loss_prodes_wdpa_yearly: ForestChangeDiagnosticDataLossYearly,
tree_cover_loss_prodes_primary_forest_yearly: ForestChangeDiagnosticDataLossYearly,
country_code: ForestChangeDiagnosticDataDoubleCategory,
tree_cover_loss_country_specific_yearly: ForestChangeDiagnosticDataLossApproxYearly,
tree_cover_loss_country_specific_wdpa_yearly: ForestChangeDiagnosticDataLossApproxYearly,
tree_cover_loss_country_specific_primary_forest_yearly: ForestChangeDiagnosticDataLossApproxYearly,
// The "country_specific_deforestation_*" fields use the country-specific
// deforestation data, which may have approximate years (because some
// deforestation is only specified for a range of years)
country_specific_deforestation_yearly: ForestChangeDiagnosticDataLossApproxYearlyCategory,
country_specific_deforestation_wdpa_yearly: ForestChangeDiagnosticDataLossApproxYearlyTwoCategory,
country_specific_deforestation_landmark_yearly: ForestChangeDiagnosticDataLossApproxYearlyTwoCategory,
// This is a country-specific categorization of country-specific deforestation. For
// Argentina, it is a breakdown into OTBN categories.
country_specific_deforestation_classified_region_yearly: ForestChangeDiagnosticDataLossApproxYearlyTwoCategory,
tree_cover_loss_brazil_biomes_yearly: ForestChangeDiagnosticDataLossYearlyCategory,
tree_cover_extent_total: ForestChangeDiagnosticDataDouble,
tree_cover_extent_primary_forest: ForestChangeDiagnosticDataDouble,
Expand Down Expand Up @@ -106,6 +117,15 @@ case class ForestChangeDiagnosticData(
tree_cover_loss_protected_areas_yearly.merge(
other.tree_cover_loss_protected_areas_yearly
),
tree_cover_loss_by_country_yearly.merge(
other.tree_cover_loss_by_country_yearly
),
tree_cover_loss_by_country_wdpa_yearly.merge(
other.tree_cover_loss_by_country_wdpa_yearly
),
tree_cover_loss_by_country_landmark_yearly.merge(
other.tree_cover_loss_by_country_landmark_yearly
),
tree_cover_loss_arg_otbn_yearly.merge(
other.tree_cover_loss_arg_otbn_yearly
),
Expand All @@ -131,10 +151,10 @@ case class ForestChangeDiagnosticData(
tree_cover_loss_prodes_primary_forest_yearly.merge(
other.tree_cover_loss_prodes_primary_forest_yearly
),
country_code.merge(other.country_code),
tree_cover_loss_country_specific_yearly.merge(other.tree_cover_loss_country_specific_yearly),
tree_cover_loss_country_specific_wdpa_yearly.merge(other.tree_cover_loss_country_specific_wdpa_yearly),
tree_cover_loss_country_specific_primary_forest_yearly.merge(other.tree_cover_loss_country_specific_primary_forest_yearly),
country_specific_deforestation_yearly.merge(other.country_specific_deforestation_yearly),
country_specific_deforestation_wdpa_yearly.merge(other.country_specific_deforestation_wdpa_yearly),
country_specific_deforestation_landmark_yearly.merge(other.country_specific_deforestation_landmark_yearly),
country_specific_deforestation_classified_region_yearly.merge(other.country_specific_deforestation_classified_region_yearly),
tree_cover_loss_brazil_biomes_yearly.merge(other.tree_cover_loss_brazil_biomes_yearly),
tree_cover_extent_total.merge(other.tree_cover_extent_total),
tree_cover_extent_primary_forest.merge(other.tree_cover_extent_primary_forest),
Expand Down Expand Up @@ -300,6 +320,9 @@ object ForestChangeDiagnosticData {
ForestChangeDiagnosticDataLossYearly.empty,
ForestChangeDiagnosticDataLossYearly.empty,
ForestChangeDiagnosticDataLossYearlyCategory.empty,
ForestChangeDiagnosticDataLossYearlyTwoCategory.empty,
ForestChangeDiagnosticDataLossYearlyTwoCategory.empty,
ForestChangeDiagnosticDataLossYearlyCategory.empty,
ForestChangeDiagnosticDataLossYearlyCategory.empty,
ForestChangeDiagnosticDataLossYearlyCategory.empty,
ForestChangeDiagnosticDataLossYearly.empty,
Expand All @@ -308,10 +331,10 @@ object ForestChangeDiagnosticData {
ForestChangeDiagnosticDataLossYearly.empty,
ForestChangeDiagnosticDataLossYearly.empty,
ForestChangeDiagnosticDataLossYearly.empty,
ForestChangeDiagnosticDataDoubleCategory.empty,
ForestChangeDiagnosticDataLossApproxYearly.empty,
ForestChangeDiagnosticDataLossApproxYearly.empty,
ForestChangeDiagnosticDataLossApproxYearly.empty,
ForestChangeDiagnosticDataLossApproxYearlyCategory.empty,
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory.empty,
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory.empty,
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory.empty,
ForestChangeDiagnosticDataLossYearlyCategory.empty,
ForestChangeDiagnosticDataDouble.empty,
ForestChangeDiagnosticDataDouble.empty,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import frameless.Injection

import scala.collection.immutable.SortedMap
import io.circe.syntax._
import io.circe.parser.decode
import cats.kernel.Semigroup
import cats.implicits._

Expand Down Expand Up @@ -45,10 +46,10 @@ case class ForestChangeDiagnosticDataLossApproxYearly(value: SortedMap[ApproxYea
SortedMap(out.toSeq: _*)
}

def round(m: SortedMap[String, Double]): SortedMap[String, Double] = m.map { case (key, value) => key -> this.round(value) }
def formatAndRound: SortedMap[String, Double] = this.combineApprox.map { case (key, value) => key -> this.round(value) }

def toJson: String = {
this.round(this.combineApprox).asJson.noSpaces
this.formatAndRound.asJson.noSpaces
}
}

Expand Down Expand Up @@ -89,9 +90,8 @@ object ForestChangeDiagnosticDataLossApproxYearly {
}

def fromString(value: String): ForestChangeDiagnosticDataLossApproxYearly = {
// We don't bother deriving the decoder for SortedMap[ApproxYear, Double], since
// we don't ever decode any ForestChangeDiagnosticDataLossApproxYearly jsons.
ForestChangeDiagnosticDataLossApproxYearly(SortedMap())
val sortedMap = decode[SortedMap[ApproxYear, Double]](value)
ForestChangeDiagnosticDataLossApproxYearly(sortedMap.getOrElse(SortedMap()))
}

implicit def injection: Injection[ForestChangeDiagnosticDataLossApproxYearly, String] = Injection(_.toJson, fromString)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package org.globalforestwatch.summarystats.forest_change_diagnostic

import frameless.Injection
import io.circe.syntax._
import io.circe.parser.decode

import org.globalforestwatch.layers.ApproxYear

// One level of categorization that leads to approximate yearly forest loss objects
// (ForestChangeDiagnosticDataLossApproxYearly).
case class ForestChangeDiagnosticDataLossApproxYearlyCategory(
value: Map[String, ForestChangeDiagnosticDataLossApproxYearly]
) extends ForestChangeDiagnosticDataParser[
ForestChangeDiagnosticDataLossApproxYearlyCategory
] {
def merge(
other: ForestChangeDiagnosticDataLossApproxYearlyCategory
): ForestChangeDiagnosticDataLossApproxYearlyCategory = {

ForestChangeDiagnosticDataLossApproxYearlyCategory(value ++ other.value.map {
case (key, otherValue) =>
key -> value
.getOrElse(key, ForestChangeDiagnosticDataLossApproxYearly.empty)
.merge(otherValue)
})
}

def formatAndRound: Map[String, Map[String, Double]] =
this.value.map {
case (key, value) =>
key -> value.formatAndRound
}

def toJson: String = {
val x = this.formatAndRound
x.asJson
.noSpaces
}
}

object ForestChangeDiagnosticDataLossApproxYearlyCategory {
def empty: ForestChangeDiagnosticDataLossApproxYearlyCategory =
ForestChangeDiagnosticDataLossApproxYearlyCategory(Map())

// There is a breakdown by one level of category before the breakdown into
// approx-yearly forest loss.
def fill(
className: String,
lossYear: ApproxYear,
areaHa: Double,
noData: List[String] = List("", "Unknown", "Not applicable"),
include: Boolean = true
): ForestChangeDiagnosticDataLossApproxYearlyCategory = {

if (noData.contains(className))
ForestChangeDiagnosticDataLossApproxYearlyCategory.empty
else
ForestChangeDiagnosticDataLossApproxYearlyCategory(
Map(
className -> ForestChangeDiagnosticDataLossApproxYearly
.fill(lossYear, areaHa, include)
)
)
}

def fromString(
value: String
): ForestChangeDiagnosticDataLossApproxYearlyCategory = {

val categories: Map[String, String] =
decode[Map[String, String]](value).getOrElse(Map())
val newValues = categories.map {
case (k, v) => (k, ForestChangeDiagnosticDataLossApproxYearly.fromString(v))
}

ForestChangeDiagnosticDataLossApproxYearlyCategory(newValues)

}

// See https://typelevel.org/frameless/Injection.html and
// https://typelevel.org/frameless/TypedEncoder.html
// Has an implicit TypedEncoder based on this injection in package.scala
implicit def injection: Injection[ForestChangeDiagnosticDataLossApproxYearlyCategory, String] = Injection(_.toJson, fromString)
//implicit def convertToString(f: ForestChangeDiagnosticDataLossApproxYearlyCategory): String = f.toJson

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.globalforestwatch.summarystats.forest_change_diagnostic

import frameless.Injection
import io.circe.syntax._
import io.circe.parser.decode

import org.globalforestwatch.layers.ApproxYear

// Two levels of categorization that lead to approximate yearly forest loss objects
// (ForestChangeDiagnosticDataLossApproxYearly).
case class ForestChangeDiagnosticDataLossApproxYearlyTwoCategory(
value: Map[String, ForestChangeDiagnosticDataLossApproxYearlyCategory]
) extends ForestChangeDiagnosticDataParser[
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory
] {
def merge(
other: ForestChangeDiagnosticDataLossApproxYearlyTwoCategory
): ForestChangeDiagnosticDataLossApproxYearlyTwoCategory = {

ForestChangeDiagnosticDataLossApproxYearlyTwoCategory(value ++ other.value.map {
case (key, otherValue) =>
key -> value
.getOrElse(key, ForestChangeDiagnosticDataLossApproxYearlyCategory.empty)
.merge(otherValue)
})
}

def toJson: String = {
val x = this.value
.map {
case (key, value) =>
key -> value.formatAndRound
}

x.asJson
.noSpaces
}
}

object ForestChangeDiagnosticDataLossApproxYearlyTwoCategory {
def empty: ForestChangeDiagnosticDataLossApproxYearlyTwoCategory =
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory(Map())

// There is a breakdown by two levels of categories before the breakdown into
// approx-yearly forest loss.
def fill(
categoryName: String,
categoryName2: String,
lossYear: ApproxYear,
areaHa: Double,
noData: List[String] = List("", "Unknown", "Not applicable"),
include: Boolean = true
): ForestChangeDiagnosticDataLossApproxYearlyTwoCategory = {

if (noData.contains(categoryName))
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory.empty
else
ForestChangeDiagnosticDataLossApproxYearlyTwoCategory(
Map(
categoryName -> ForestChangeDiagnosticDataLossApproxYearlyCategory
.fill(categoryName2, lossYear, areaHa, include = include)
)
)
}

def fromString(
value: String
): ForestChangeDiagnosticDataLossApproxYearlyTwoCategory = {

val categories: Map[String, String] =
decode[Map[String, String]](value).getOrElse(Map())
val newValues = categories.map {
case (k, v) => (k, ForestChangeDiagnosticDataLossApproxYearlyCategory.fromString(v))
}

ForestChangeDiagnosticDataLossApproxYearlyTwoCategory(newValues)

}

// See https://typelevel.org/frameless/Injection.html and
// https://typelevel.org/frameless/TypedEncoder.html
// Has an implicit TypedEncoder based on this injection in package.scala
implicit def injection: Injection[ForestChangeDiagnosticDataLossApproxYearlyTwoCategory, String] = Injection(_.toJson, fromString)
}
Loading

0 comments on commit b404d97

Please sign in to comment.