Skip to content

Commit

Permalink
Merge pull request #3032 from CloudNiner/bugfix/awf/tilelayermetadata…
Browse files Browse the repository at this point in the history
…-json-codecs#3029

Fix Circe decoders for TileLayerMetadata Schema and Metadata.layoutDefinition
  • Loading branch information
pomadchin authored Jul 19, 2019
2 parents ef382c9 + 0529c3f commit 0e60b86
Show file tree
Hide file tree
Showing 4 changed files with 118 additions and 6 deletions.
18 changes: 15 additions & 3 deletions layer/src/main/scala/geotrellis/layer/TileLayerMetadata.scala
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ package geotrellis.layer

import geotrellis.proj4.CRS
import geotrellis.raster._
import geotrellis.layer._
import geotrellis.util._
import geotrellis.vector.Extent
import geotrellis.vector.io.json.CrsFormats._

import cats.{Functor, Semigroup}
import cats.syntax.functor._
Expand Down Expand Up @@ -87,8 +87,20 @@ case class TileLayerMetadata[K](
}

object TileLayerMetadata {
implicit def tileLayerMetadataEncoder[K: SpatialComponent: Encoder]: Encoder[TileLayerMetadata[K]] = deriveEncoder
implicit def tileLayerMetadataDecoder[K: SpatialComponent: Decoder]: Decoder[TileLayerMetadata[K]] = deriveDecoder
implicit def tileLayerMetadataEncoder[K: SpatialComponent: Encoder]: Encoder[TileLayerMetadata[K]] =
Encoder.forProduct5(
"cellType",
"layoutDefinition",
"extent",
"crs",
"bounds")(ld => (ld.cellType, ld.layout, ld.extent, ld.crs, ld.bounds))
implicit def tileLayerMetadataDecoder[K: SpatialComponent: Decoder]: Decoder[TileLayerMetadata[K]] =
Decoder.forProduct5(
"cellType",
"layoutDefinition",
"extent",
"crs",
"bounds")(TileLayerMetadata.apply)

implicit def toLayoutDefinition(md: TileLayerMetadata[_]): LayoutDefinition =
md.layout
Expand Down
63 changes: 63 additions & 0 deletions layer/src/test/scala/geotrellis/layer/TileLayerMetadataSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright 2019 Azavea
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package geotrellis.layer

import geotrellis.raster._
import geotrellis.proj4._
import geotrellis.vector._
import _root_.io.circe.syntax._
import _root_.io.circe.parser.decode
import geotrellis.raster
import org.scalatest._

class TileLayerMetadataSpec extends FunSpec with Matchers {
describe("TileLayerMetadata JSON codecs") {

val cellType = DoubleCellType
val layoutDefinition = LayoutDefinition(Extent(-126.0, 21.608163265306125, -62.0, 54.0),
TileLayout(4, 2, 256, 256))
val extent = Extent(-126.0, 23.0, -66.0, 54.0)
val bounds = KeyBounds(SpaceTimeKey(0,0,0), SpaceTimeKey(3,1,1000000))
val tileLayerMetadata: TileLayerMetadata[SpaceTimeKey] = TileLayerMetadata(
cellType,
layoutDefinition,
extent,
LatLng,
bounds
)

it("should convert TileLayerMetadata to json and back with specific JSON keys") {
val json = tileLayerMetadata.asJson
json.hcursor.downField("cellType").as[CellType] should equal(Right(cellType))
json.hcursor.downField("layoutDefinition").as[LayoutDefinition] should equal(Right(layoutDefinition))
json.hcursor.downField("extent").as[Extent] should equal(Right(extent))
json.hcursor.downField("crs").as[CRS] should equal(Right(LatLng))
json.hcursor.downField("bounds").as[Bounds[SpaceTimeKey]] should equal(Right(bounds))

json.as[TileLayerMetadata[SpaceTimeKey]].right.map(_ should equal(tileLayerMetadata))
}

it("should parse a GeoTrellis 1.2 TileLayerMetadata object") {
// NOTE: If this test breaks because the keys in TileLayerMetadata have changed, then
// we've broken backwards compatibility in reading TileLayerMetadata in our layers.
val metadata: String = "{\"extent\":{\"xmin\":-126.0,\"ymin\":23.0,\"xmax\":-66.0,\"ymax\":54.0},\"layoutDefinition\":{\"extent\":{\"xmin\":-126.0,\"ymin\":21.608163265306125,\"xmax\":-62.0,\"ymax\":54.0},\"tileLayout\":{\"layoutCols\":4,\"layoutRows\":2,\"tileCols\":256,\"tileRows\":256}},\"bounds\":{\"minKey\":{\"col\":0,\"row\":0,\"instant\":1514764800000},\"maxKey\":{\"col\":3,\"row\":1,\"instant\":4133894400000}},\"cellType\":\"float32ud1.0000000150474662E30\",\"crs\":\"+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs \"}"
val result = decode[TileLayerMetadata[SpaceTimeKey]](metadata)
result.isRight should equal(true)
}
}
}

7 changes: 4 additions & 3 deletions store/src/main/scala/geotrellis/store/json/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import geotrellis.vector._
import geotrellis.vector.io.json.CrsFormats

import _root_.io.circe._
import _root_.io.circe.parser._
import _root_.io.circe.syntax._
import cats.syntax.either._
import org.apache.avro.Schema
Expand Down Expand Up @@ -99,9 +100,9 @@ trait Implicits extends KeyIndexFormats with CrsFormats {
}

implicit val schemaEncoder: Encoder[Schema] =
Encoder.encodeString.contramap[Schema] { _.toString }
Encoder.encodeJson.contramap[Schema] { schema => parse(schema.toString).valueOr(throw _) }
implicit val schemaDecoder: Decoder[Schema] =
Decoder.decodeString.emap { str =>
Either.catchNonFatal((new Schema.Parser).parse(str)).leftMap(_ => "Schema expected")
Decoder.decodeJson.emap { json =>
Either.catchNonFatal((new Schema.Parser).parse(json.noSpaces)).leftMap(_ => "Schema expected")
}
}
36 changes: 36 additions & 0 deletions store/src/test/scala/geotrellis/store/json/SchemaJsonSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2016 Azavea
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package geotrellis.store.json


import cats.syntax.either._
import geotrellis.store.json.Implicits._
import io.circe._
import io.circe.parser._
import org.apache.avro.Schema
import org.scalatest._

class SchemaJsonSpec extends FunSpec with Matchers {
describe("KeyIndexJsonFormatFactory") {
val schemaString = "{\"type\":\"record\",\"name\":\"KeyValueRecord\",\"namespace\":\"geotrellis.spark.io\",\"fields\":[{\"name\":\"pairs\",\"type\":{\"type\":\"array\",\"items\":{\"type\":\"record\",\"name\":\"Tuple2\",\"namespace\":\"scala\",\"fields\":[{\"name\":\"_1\",\"type\":{\"type\":\"record\",\"name\":\"SpaceTimeKey\",\"namespace\":\"geotrellis.spark\",\"fields\":[{\"name\":\"col\",\"type\":\"int\"},{\"name\":\"row\",\"type\":\"int\"},{\"name\":\"instant\",\"type\":\"long\",\"aliases\":[\"millis\"]}]}},{\"name\":\"_2\",\"type\":{\"type\":\"record\",\"name\":\"ArrayMultibandTile\",\"namespace\":\"geotrellis.raster\",\"fields\":[{\"name\":\"bands\",\"type\":{\"type\":\"array\",\"items\":[{\"type\":\"record\",\"name\":\"ByteArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":\"bytes\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":-128}]},{\"type\":\"record\",\"name\":\"FloatArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":{\"type\":\"array\",\"items\":\"float\"}},{\"name\":\"noDataValue\",\"type\":[\"boolean\",\"float\"],\"default\":true}]},{\"type\":\"record\",\"name\":\"DoubleArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":{\"type\":\"array\",\"items\":\"double\"}},{\"name\":\"noDataValue\",\"type\":[\"boolean\",\"double\"],\"default\":true}]},{\"type\":\"record\",\"name\":\"ShortArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":{\"type\":\"array\",\"items\":\"int\"}},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":-32768}]},{\"type\":\"record\",\"name\":\"IntArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":{\"type\":\"array\",\"items\":\"int\"}},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":-2147483648}]},{\"type\":\"record\",\"name\":\"BitArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":\"bytes\"}]},{\"type\":\"record\",\"name\":\"UByteArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":\"bytes\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":0}]},{\"type\":\"record\",\"name\":\"UShortArrayTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cells\",\"type\":{\"type\":\"array\",\"items\":\"int\"}},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":0}]},{\"type\":\"record\",\"name\":\"ByteConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"int\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":-128}]},{\"type\":\"record\",\"name\":\"FloatConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"float\"},{\"name\":\"noDataValue\",\"type\":[\"boolean\",\"float\"],\"default\":true}]},{\"type\":\"record\",\"name\":\"DoubleConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"double\"},{\"name\":\"noDataValue\",\"type\":[\"boolean\",\"double\"],\"default\":true}]},{\"type\":\"record\",\"name\":\"ShortConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"int\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":0}]},{\"type\":\"record\",\"name\":\"IntConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"int\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":-2147483648}]},{\"type\":\"record\",\"name\":\"BitConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"boolean\"}]},{\"type\":\"record\",\"name\":\"UByteConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"int\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":0}]},{\"type\":\"record\",\"name\":\"UShortConstantTile\",\"fields\":[{\"name\":\"cols\",\"type\":\"int\"},{\"name\":\"rows\",\"type\":\"int\"},{\"name\":\"cell\",\"type\":\"int\"},{\"name\":\"noDataValue\",\"type\":[\"int\",\"null\"],\"default\":0}]}]}}]}}]}}}]}"
it("should be able to decode Schema from generic Json, NOT String") {
val json: Json = parse(schemaString).valueOr(throw _)
val schema = json.as[Schema].valueOr(throw _)
}
}
}

0 comments on commit 0e60b86

Please sign in to comment.