From c2383d7b3ec0445bd3b0aac83df9c5cb79ba5ea4 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 10:40:11 +0200 Subject: [PATCH 01/23] Update dependencies, add Scala Native support for circe integration, add jsoniter-scala integration --- .github/workflows/ci.yml | 38 ++--- build.sbt | 25 ++- .../geoscala/CirceDecodingTests.scala | 5 + .../free2move/geoscala/jsoniter_scala.scala | 152 ++++++++++++++++++ .../geoscala/JsoniterScalaDecodingTests.scala | 89 ++++++++++ project/plugins.sbt | 5 +- 6 files changed, 287 insertions(+), 27 deletions(-) create mode 100644 jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala create mode 100644 jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01458c7..e3f363f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.16, 2.13.8, 3.1.3] + scala: [2.12.17, 2.13.8, 3.2.0] java: [temurin@8] project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} @@ -151,32 +151,32 @@ jobs: ~/Library/Caches/Coursier/v1 key: ${{ runner.os }}-sbt-cache-v2-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('project/build.properties') }} - - name: Download target directories (2.12.16, rootJS) + - name: Download target directories (2.12.17, rootJS) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootJS - - name: Inflate target directories (2.12.16, rootJS) + - name: Inflate target directories (2.12.17, rootJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.12.16, rootJVM) + - name: Download target directories (2.12.17, rootJVM) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootJVM - - name: Inflate target directories (2.12.16, rootJVM) + - name: Inflate target directories (2.12.17, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.12.16, rootNative) + - name: Download target directories (2.12.17, rootNative) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.16-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.12.17-rootNative - - name: Inflate target directories (2.12.16, rootNative) + - name: Inflate target directories (2.12.17, rootNative) run: | tar xf targets.tar rm targets.tar @@ -211,32 +211,32 @@ jobs: tar xf targets.tar rm targets.tar - - name: Download target directories (3.1.3, rootJS) + - name: Download target directories (3.2.0, rootJS) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootJS - - name: Inflate target directories (3.1.3, rootJS) + - name: Inflate target directories (3.2.0, rootJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3.1.3, rootJVM) + - name: Download target directories (3.2.0, rootJVM) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootJVM - - name: Inflate target directories (3.1.3, rootJVM) + - name: Inflate target directories (3.2.0, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (3.1.3, rootNative) + - name: Download target directories (3.2.0, rootNative) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-3.1.3-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.0-rootNative - - name: Inflate target directories (3.1.3, rootNative) + - name: Inflate target directories (3.2.0, rootNative) run: | tar xf targets.tar rm targets.tar diff --git a/build.sbt b/build.sbt index 5c0073f..6a53d7c 100644 --- a/build.sbt +++ b/build.sbt @@ -1,6 +1,6 @@ -val scala212 = "2.12.16" +val scala212 = "2.12.17" val scala213 = "2.13.8" -val scala3 = "3.1.3" +val scala3 = "3.2.0" ThisBuild / tlBaseVersion := "0.2" @@ -17,11 +17,11 @@ ThisBuild / tlSonatypeUseLegacyHost := true ThisBuild / crossScalaVersions := Seq(scala212, scala213, scala3) ThisBuild / scalaVersion := scala213 // the default Scala -lazy val root = tlCrossRootProject.aggregate(core, circe, polyline).settings(name := "geo-scala") +lazy val root = tlCrossRootProject.aggregate(core, circe, jsoniterScala, polyline).settings(name := "geo-scala") lazy val commonSettings = Seq( libraryDependencies ++= Seq( - "org.scalatest" %%% "scalatest" % "3.2.12" % Test, + "org.scalatest" %%% "scalatest" % "3.2.13" % Test, "org.scalatestplus" %%% "scalacheck-1-16" % "3.2.13.0" % Test, "org.scalacheck" %%% "scalacheck" % "1.16.0" % Test ), @@ -39,8 +39,8 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) ) ) -val circeVersion = "0.14.2" -lazy val circe = crossProject(JVMPlatform, JSPlatform) +val circeVersion = "0.14.3" +lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .crossType(CrossType.Pure) .in(file("circe")) .dependsOn(core) @@ -52,6 +52,19 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform) ) ) +val jsoniterScalaVersion = "2.17.4" +lazy val jsoniterScala = crossProject(JVMPlatform, JSPlatform, NativePlatform) + .crossType(CrossType.Pure) + .in(file("jsoniter-scala")) + .dependsOn(core) + .settings( + commonSettings ++ Seq( + name := "geo-scala-jsoniter-scala", + libraryDependencies += "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % jsoniterScalaVersion, + libraryDependencies += "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % jsoniterScalaVersion % Provided + ) + ) + lazy val polyline = crossProject(JVMPlatform, JSPlatform, NativePlatform) .in(file("polyline")) .dependsOn(core) diff --git a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala index f674043..df979da 100644 --- a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala +++ b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala @@ -79,6 +79,11 @@ class CirceDecodingTests extends AnyFlatSpec with Matchers with EitherValues { List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775)))) ) ) + parser.decode[GeoJson[Json]](json) shouldBe Right( + FeatureCollection( + List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775)))) + ) + ) } } diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala new file mode 100644 index 0000000..284641e --- /dev/null +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -0,0 +1,152 @@ +/* + * Copyright 2019 GHM Mobile Development GmbH + * + * 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 com.free2move.geoscala + +import com.github.plokhotnyuk.jsoniter_scala.core._ +import com.github.plokhotnyuk.jsoniter_scala.macros._ + +object jsoniter_scala { + // Uncomment for printing of generating codecs + // implicit val printCodec: CodecMakerConfig.PrintCodec = new CodecMakerConfig.PrintCodec {} + + implicit val coordinateCodec: JsonValueCodec[Coordinate] = + new JsonValueCodec[Coordinate] { + override def decodeValue(in: JsonReader, default: Coordinate): Coordinate = + if (in.isNextToken('[')) { + val lon = in.readDouble() + if (!in.isNextToken(',')) in.commaError() + val lat = in.readDouble() + while (in.isNextToken(',')) in.skip() + if (!in.isCurrentToken(']')) in.arrayEndOrCommaError() + Coordinate(lon, lat) + } else in.readNullOrTokenError(default, '[') + + override def encodeValue(x: Coordinate, out: JsonWriter): Unit = { + out.writeArrayStart() + out.writeVal(x.longitude) + out.writeVal(x.latitude) + out.writeArrayEnd() + } + + override def nullValue: Coordinate = null + } + + implicit val listOfCoordinatesCodec: JsonValueCodec[List[Coordinate]] = + JsonCodecMaker.make + + implicit val listOfListOfCoordinatesCodec: JsonValueCodec[List[List[Coordinate]]] = + JsonCodecMaker.make + + implicit val listOfListOfListOfCoordinatesCodec: JsonValueCodec[List[List[List[Coordinate]]]] = + JsonCodecMaker.make + + implicit val pointCodec: JsonValueCodec[Point] = + makeGeometryCodec("Point", _.coordinates, Point) + + implicit val multiPointCodec: JsonValueCodec[MultiPoint] = + makeGeometryCodec("MultiPoint", _.coordinates, MultiPoint) + + implicit val lineStringCodec: JsonValueCodec[LineString] = + makeGeometryCodec("LineString", _.coordinates, LineString) + + implicit val multiLineStringCodec: JsonValueCodec[MultiLineString] = + makeGeometryCodec("MultiLineString", _.coordinates, MultiLineString) + + implicit val polygonCodec: JsonValueCodec[Polygon] = + makeGeometryCodec("Polygon", _.coordinates, Polygon) + + implicit val multiPolygonCodec: JsonValueCodec[MultiPolygon] = + makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon) + + private def makeGeometryCodec[C: JsonValueCodec, G <: Geometry](`type`: String, coords: G => C, geom: C => G): JsonValueCodec[G] = + new JsonValueCodec[G] { + private val coordinatesCodec: JsonValueCodec[C] = implicitly[JsonValueCodec[C]] + + override def decodeValue(in: JsonReader, default: G): G = + if (in.isNextToken('{')) { + var coordinates: C = coordinatesCodec.nullValue + var mask = 3 + var len = -1 + while (len < 0 || in.isNextToken(',')) { + len = in.readKeyAsCharBuf() + if (in.isCharBufEqualsTo(len, "type")) { + if ((mask & 0x1) != 0) mask ^= 0x1 + else in.duplicatedKeyError(len) + len = in.readStringAsCharBuf() + if (!in.isCharBufEqualsTo(len, `type`)) in.discriminatorValueError("type") + } else if (in.isCharBufEqualsTo(len, "coordinates")) { + if ((mask & 0x2) != 0) mask ^= 0x2 + else in.duplicatedKeyError(len) + coordinates = coordinatesCodec.decodeValue(in, coordinates) + } else in.skip() + } + geom(coordinates) + } else in.readNullOrTokenError(default, '}') + + override def encodeValue(x: G, out: JsonWriter): Unit = { + out.writeObjectStart() + out.writeNonEscapedAsciiKey("type") + out.writeNonEscapedAsciiVal(`type`) + out.writeNonEscapedAsciiKey("coordinates") + coordinatesCodec.encodeValue(coords(x), out) + out.writeObjectEnd() + } + + override def nullValue: G = null.asInstanceOf[G] + } + + implicit val geometryCodec: JsonValueCodec[Geometry] = + JsonCodecMaker.make + + implicit def featureCodec[P: JsonValueCodec]: JsonValueCodec[Feature[P]] = + JsonCodecMaker.make + + implicit def featureCollectionCodec[P: JsonValueCodec]: JsonValueCodec[FeatureCollection[P]] = + JsonCodecMaker.make + + implicit def geoJson[P: JsonValueCodec]: JsonValueCodec[GeoJson[P]] = + new JsonValueCodec[GeoJson[P]] { + private val fc: JsonValueCodec[Feature[P]] = featureCodec + private val fcc: JsonValueCodec[FeatureCollection[P]] = featureCollectionCodec + + override def decodeValue(in: JsonReader, default: GeoJson[P]): GeoJson[P] = { + in.setMark() + if (in.isNextToken('{')) { + if (!in.skipToKey("type")) in.discriminatorError() + val len = in.readStringAsCharBuf() + in.rollbackToMark() + if (in.isCharBufEqualsTo(len, "Feature")) fc.decodeValue(in, fc.nullValue) + else if (in.isCharBufEqualsTo(len, "FeatureCollection")) fcc.decodeValue(in, fcc.nullValue) + else geometryCodec.decodeValue(in, geometryCodec.nullValue).asInstanceOf[GeoJson[P]] + } else { + val gj = in.readNullOrTokenError(default, '{') + in.rollbackToMark() + in.skip() + gj + } + } + + override def encodeValue(x: GeoJson[P], out: JsonWriter): Unit = + x match { + case f: Feature[P] => fc.encodeValue(f, out) + case fc: FeatureCollection[P] => fcc.encodeValue(fc, out) + case _ => geometryCodec.encodeValue(x.asInstanceOf[Geometry], out) + } + + override def nullValue: GeoJson[P] = null.asInstanceOf[GeoJson[P]] + } +} diff --git a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala new file mode 100644 index 0000000..f66b682 --- /dev/null +++ b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala @@ -0,0 +1,89 @@ +/* + * Copyright 2019 GHM Mobile Development GmbH + * + * 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 com.free2move.geoscala + +import com.github.plokhotnyuk.jsoniter_scala.core._ +import com.github.plokhotnyuk.jsoniter_scala.macros._ +import org.scalatest.EitherValues +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class JsoniterScalaDecodingTests extends AnyFlatSpec with Matchers with EitherValues { + + import com.free2move.geoscala.jsoniter_scala._ + + "The jsoniter-scala codecs" should "handle simple 2D points" in { + val json = + """{ + "type": "Point", + "coordinates": [ + 12.3046875, + 51.8357775 + ] + }""" + readFromString[Point](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) + readFromString[Geometry](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) + } + + it should "handle points with more dimensions" in { + val json = + """{ + "type": "Point", + "coordinates": [ + 12.3046875, + 51.8357775, + 7.000, + 42.12345 + ] + }""" + readFromString[Point](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) + readFromString[Geometry](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) + } + + it should "handle FeatureCollection without Properties as pure JSON correctly" in { + type Json = Map[String, Int] + + implicit val jsonCodec: JsonValueCodec[Json] = JsonCodecMaker.make + + val json = + """{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "id": 7 + }, + "geometry": { + "type": "Point", + "coordinates": [ + 12.3046875, + 51.8357775 + ] + } + } + ] + }""" + readFromString[FeatureCollection[Json]](json) shouldBe FeatureCollection( + List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775)))) + ) + readFromString[GeoJson[Json]](json) shouldBe FeatureCollection( + List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775)))) + ) + } + +} diff --git a/project/plugins.sbt b/project/plugins.sbt index cdb11b1..095ac22 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,4 +1,5 @@ addSbtPlugin("org.typelevel" % "sbt-typelevel" % "0.4.13") -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.10.1") -addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.5") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.11.0") +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.2.0") +addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.7") addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.2.0") From bffae25c02e021e072f7555e799f3666986e6565 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 11:26:39 +0200 Subject: [PATCH 02/23] Update tlBaseVersion to 0.3.0 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 6a53d7c..d85f982 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val scala212 = "2.12.17" val scala213 = "2.13.8" val scala3 = "3.2.0" -ThisBuild / tlBaseVersion := "0.2" +ThisBuild / tlBaseVersion := "0.3.0" ThisBuild / organization := "org.gnieh" ThisBuild / organizationName := "GHM Mobile Development GmbH" From dc72ec8588ad23e6b5b43a1fc6b337eff98e4d3d Mon Sep 17 00:00:00 2001 From: Yannick Heiber Date: Mon, 19 Sep 2022 11:35:01 +0200 Subject: [PATCH 03/23] Update build.sbt --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index d85f982..23e7a98 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val scala212 = "2.12.17" val scala213 = "2.13.8" val scala3 = "3.2.0" -ThisBuild / tlBaseVersion := "0.3.0" +ThisBuild / tlBaseVersion := "0.3" ThisBuild / organization := "org.gnieh" ThisBuild / organizationName := "GHM Mobile Development GmbH" From 2a7f7001fc249620f444921ddea8cc5fb998b5cd Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 11:42:05 +0200 Subject: [PATCH 04/23] Update result of `sbt githubWorkflowGenerate` --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3f363f..c1ae72a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,11 +94,11 @@ jobs: - name: Make target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: mkdir -p circe/.jvm/target target polyline/js/target .js/target core/.native/target core/.js/target circe/.js/target core/.jvm/target .jvm/target .native/target polyline/jvm/target polyline/native/target project/target + run: mkdir -p jsoniter-scala/.native/target circe/.jvm/target target polyline/js/target .js/target core/.native/target core/.js/target circe/.js/target core/.jvm/target .jvm/target .native/target polyline/jvm/target circe/.native/target jsoniter-scala/.jvm/target polyline/native/target jsoniter-scala/.js/target project/target - name: Compress target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') - run: tar cf targets.tar circe/.jvm/target target polyline/js/target .js/target core/.native/target core/.js/target circe/.js/target core/.jvm/target .jvm/target .native/target polyline/jvm/target polyline/native/target project/target + run: tar cf targets.tar jsoniter-scala/.native/target circe/.jvm/target target polyline/js/target .js/target core/.native/target core/.js/target circe/.js/target core/.jvm/target .jvm/target .native/target polyline/jvm/target circe/.native/target jsoniter-scala/.jvm/target polyline/native/target jsoniter-scala/.js/target project/target - name: Upload target directories if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main') From 6e1ef9f9af72e9880e72dd0318dc5039001908d9 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 11:54:24 +0200 Subject: [PATCH 05/23] Fix compilation errors with Scala 3 --- .../com/free2move/geoscala/jsoniter_scala.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index 284641e..6143874 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -55,22 +55,22 @@ object jsoniter_scala { JsonCodecMaker.make implicit val pointCodec: JsonValueCodec[Point] = - makeGeometryCodec("Point", _.coordinates, Point) + makeGeometryCodec("Point", _.coordinates, Point.apply) implicit val multiPointCodec: JsonValueCodec[MultiPoint] = - makeGeometryCodec("MultiPoint", _.coordinates, MultiPoint) + makeGeometryCodec("MultiPoint", _.coordinates, MultiPoint.apply) implicit val lineStringCodec: JsonValueCodec[LineString] = - makeGeometryCodec("LineString", _.coordinates, LineString) + makeGeometryCodec("LineString", _.coordinates, LineString.apply) implicit val multiLineStringCodec: JsonValueCodec[MultiLineString] = - makeGeometryCodec("MultiLineString", _.coordinates, MultiLineString) + makeGeometryCodec("MultiLineString", _.coordinates, MultiLineString.apply) implicit val polygonCodec: JsonValueCodec[Polygon] = - makeGeometryCodec("Polygon", _.coordinates, Polygon) + makeGeometryCodec("Polygon", _.coordinates, Polygon.apply) implicit val multiPolygonCodec: JsonValueCodec[MultiPolygon] = - makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon) + makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon.apply) private def makeGeometryCodec[C: JsonValueCodec, G <: Geometry](`type`: String, coords: G => C, geom: C => G): JsonValueCodec[G] = new JsonValueCodec[G] { From df255c4336daf0c167c926869b158d7f12c888c0 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 12:19:31 +0200 Subject: [PATCH 06/23] Disable expected warnings --- .../free2move/geoscala/jsoniter_scala.scala | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index 6143874..a1da459 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -18,6 +18,7 @@ package com.free2move.geoscala import com.github.plokhotnyuk.jsoniter_scala.core._ import com.github.plokhotnyuk.jsoniter_scala.macros._ +import scala.annotation.nowarn object jsoniter_scala { // Uncomment for printing of generating codecs @@ -45,14 +46,11 @@ object jsoniter_scala { override def nullValue: Coordinate = null } - implicit val listOfCoordinatesCodec: JsonValueCodec[List[Coordinate]] = - JsonCodecMaker.make + implicit val listOfCoordinatesCodec: JsonValueCodec[List[Coordinate]] = JsonCodecMaker.make - implicit val listOfListOfCoordinatesCodec: JsonValueCodec[List[List[Coordinate]]] = - JsonCodecMaker.make + implicit val listOfListOfCoordinatesCodec: JsonValueCodec[List[List[Coordinate]]] = JsonCodecMaker.make - implicit val listOfListOfListOfCoordinatesCodec: JsonValueCodec[List[List[List[Coordinate]]]] = - JsonCodecMaker.make + implicit val listOfListOfListOfCoordinatesCodec: JsonValueCodec[List[List[List[Coordinate]]]] = JsonCodecMaker.make implicit val pointCodec: JsonValueCodec[Point] = makeGeometryCodec("Point", _.coordinates, Point.apply) @@ -109,14 +107,13 @@ object jsoniter_scala { override def nullValue: G = null.asInstanceOf[G] } - implicit val geometryCodec: JsonValueCodec[Geometry] = - JsonCodecMaker.make + implicit val geometryCodec: JsonValueCodec[Geometry] = JsonCodecMaker.make - implicit def featureCodec[P: JsonValueCodec]: JsonValueCodec[Feature[P]] = - JsonCodecMaker.make + @nowarn + implicit def featureCodec[P: JsonValueCodec]: JsonValueCodec[Feature[P]] = JsonCodecMaker.make - implicit def featureCollectionCodec[P: JsonValueCodec]: JsonValueCodec[FeatureCollection[P]] = - JsonCodecMaker.make + @nowarn + implicit def featureCollectionCodec[P: JsonValueCodec]: JsonValueCodec[FeatureCollection[P]] = JsonCodecMaker.make implicit def geoJson[P: JsonValueCodec]: JsonValueCodec[GeoJson[P]] = new JsonValueCodec[GeoJson[P]] { From 191f26835efd4a7f4b8a3d720810134db70b2cd2 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 12:21:26 +0200 Subject: [PATCH 07/23] Clean up comments --- .../src/main/scala/com/free2move/geoscala/jsoniter_scala.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index a1da459..78c2f25 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -21,7 +21,7 @@ import com.github.plokhotnyuk.jsoniter_scala.macros._ import scala.annotation.nowarn object jsoniter_scala { - // Uncomment for printing of generating codecs + // Uncomment for printing of codecs generated by macros // implicit val printCodec: CodecMakerConfig.PrintCodec = new CodecMakerConfig.PrintCodec {} implicit val coordinateCodec: JsonValueCodec[Coordinate] = From d0870f8ec11276dc1d50c998c932266138736447 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Mon, 19 Sep 2022 13:12:11 +0200 Subject: [PATCH 08/23] A workaround to build Java 8 bytecode using Java 11 --- build.sbt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.sbt b/build.sbt index 23e7a98..af6b93c 100644 --- a/build.sbt +++ b/build.sbt @@ -16,6 +16,8 @@ ThisBuild / tlSonatypeUseLegacyHost := true ThisBuild / crossScalaVersions := Seq(scala212, scala213, scala3) ThisBuild / scalaVersion := scala213 // the default Scala +ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11")) +ThisBuild / tlJdkRelease := Some(8) lazy val root = tlCrossRootProject.aggregate(core, circe, jsoniterScala, polyline).settings(name := "geo-scala") From 876e75ca8c5766944449eaa54ad4a90cbba01c78 Mon Sep 17 00:00:00 2001 From: Yannick Heiber Date: Mon, 19 Sep 2022 15:18:07 +0200 Subject: [PATCH 09/23] Run githubWorkflowGenerate to reflect JDK versions --- .github/workflows/ci.yml | 42 ++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1ae72a..c8d71b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: matrix: os: [ubuntu-latest] scala: [2.12.17, 2.13.8, 3.2.0] - java: [temurin@8] + java: [temurin@11] project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} steps: @@ -38,21 +38,21 @@ jobs: with: fetch-depth: 0 - - name: Download Java (temurin@8) - id: download-java-temurin-8 - if: matrix.java == 'temurin@8' + - name: Download Java (temurin@11) + id: download-java-temurin-11 + if: matrix.java == 'temurin@11' uses: typelevel/download-java@v1 with: distribution: temurin - java-version: 8 + java-version: 11 - - name: Setup Java (temurin@8) - if: matrix.java == 'temurin@8' + - name: Setup Java (temurin@11) + if: matrix.java == 'temurin@11' uses: actions/setup-java@v2 with: distribution: jdkfile - java-version: 8 - jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + java-version: 11 + jdkFile: ${{ steps.download-java-temurin-11.outputs.jdkFile }} - name: Cache sbt uses: actions/cache@v2 @@ -70,7 +70,7 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' 'project /' githubWorkflowCheck - name: Check headers and formatting - if: matrix.java == 'temurin@8' + if: matrix.java == 'temurin@11' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' headerCheckAll scalafmtCheckAll 'project /' scalafmtSbtCheck - name: scalaJSLink @@ -85,11 +85,11 @@ jobs: run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' test - name: Check binary compatibility - if: matrix.java == 'temurin@8' + if: matrix.java == 'temurin@11' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' mimaReportBinaryIssues - name: Generate API documentation - if: matrix.java == 'temurin@8' + if: matrix.java == 'temurin@11' run: sbt 'project ${{ matrix.project }}' '++${{ matrix.scala }}' doc - name: Make target directories @@ -115,7 +115,7 @@ jobs: matrix: os: [ubuntu-latest] scala: [2.13.8] - java: [temurin@8] + java: [temurin@11] runs-on: ${{ matrix.os }} steps: - name: Checkout current branch (full) @@ -123,21 +123,21 @@ jobs: with: fetch-depth: 0 - - name: Download Java (temurin@8) - id: download-java-temurin-8 - if: matrix.java == 'temurin@8' + - name: Download Java (temurin@11) + id: download-java-temurin-11 + if: matrix.java == 'temurin@11' uses: typelevel/download-java@v1 with: distribution: temurin - java-version: 8 + java-version: 11 - - name: Setup Java (temurin@8) - if: matrix.java == 'temurin@8' + - name: Setup Java (temurin@11) + if: matrix.java == 'temurin@11' uses: actions/setup-java@v2 with: distribution: jdkfile - java-version: 8 - jdkFile: ${{ steps.download-java-temurin-8.outputs.jdkFile }} + java-version: 11 + jdkFile: ${{ steps.download-java-temurin-11.outputs.jdkFile }} - name: Cache sbt uses: actions/cache@v2 From 5e5b9f75dee8eb5f6bd1f031462c173901fac175 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 07:35:48 +0200 Subject: [PATCH 10/23] Code and tests clean up --- circe/src/main/scala/com/free2move/geoscala/circe.scala | 9 ++------- .../com/free2move/geoscala/CirceDecodingTests.scala | 5 +---- .../free2move/geoscala/JsoniterScalaDecodingTests.scala | 4 +--- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/circe/src/main/scala/com/free2move/geoscala/circe.scala b/circe/src/main/scala/com/free2move/geoscala/circe.scala index 2608c0c..1ee7fa5 100644 --- a/circe/src/main/scala/com/free2move/geoscala/circe.scala +++ b/circe/src/main/scala/com/free2move/geoscala/circe.scala @@ -17,12 +17,10 @@ package com.free2move.geoscala import cats.syntax.functor._ - import io.circe._ import io.circe.syntax._ trait LowPriorityGeoJsonEncoders { - implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { coord => Json.arr(Json.fromDoubleOrNull(coord.longitude), Json.fromDoubleOrNull(coord.latitude)) } @@ -71,7 +69,6 @@ trait GeoJsonEncoders extends LowPriorityGeoJsonEncoders { } trait GeoJsonDecoders { - implicit val coordinateDecoder: Decoder[Coordinate] = Decoder.instance { cursor => for { lng <- cursor.downN(0).as[Double] @@ -131,15 +128,13 @@ trait GeoJsonDecoders { } @inline - private def ensureType(cursor: HCursor, `type`: String): Decoder.Result[String] = { + private def ensureType(cursor: HCursor, tpe: String): Decoder.Result[String] = { val typeCursor = cursor.downField("type") typeCursor.as[String] match { - case Right(b) if b != `type` => Left(DecodingFailure(s"GeoJSON's type is not ${`type`}", typeCursor.history)) + case Right(b) if b != tpe => Left(DecodingFailure(s"GeoJSON's type is not $tpe", typeCursor.history)) case res => res } - } - } /** Object with implicit circe encoders and decoders. You can alternatively mixin [[GeoJsonEncoders]] and [[GeoJsonDecoders]] instead of importing. diff --git a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala index df979da..439c891 100644 --- a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala +++ b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala @@ -16,6 +16,7 @@ package com.free2move.geoscala +import com.free2move.geoscala.circe._ import io.circe._ import io.circe.syntax._ import org.scalatest.EitherValues @@ -23,9 +24,6 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers class CirceDecodingTests extends AnyFlatSpec with Matchers with EitherValues { - - import com.free2move.geoscala.circe._ - "The circe decoders" should "handle simple 2D points" in { val json = """{ @@ -85,5 +83,4 @@ class CirceDecodingTests extends AnyFlatSpec with Matchers with EitherValues { ) ) } - } diff --git a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala index f66b682..c1c7519 100644 --- a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala +++ b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala @@ -16,6 +16,7 @@ package com.free2move.geoscala +import com.free2move.geoscala.jsoniter_scala._ import com.github.plokhotnyuk.jsoniter_scala.core._ import com.github.plokhotnyuk.jsoniter_scala.macros._ import org.scalatest.EitherValues @@ -23,9 +24,6 @@ import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers class JsoniterScalaDecodingTests extends AnyFlatSpec with Matchers with EitherValues { - - import com.free2move.geoscala.jsoniter_scala._ - "The jsoniter-scala codecs" should "handle simple 2D points" in { val json = """{ From 8c7f18fdd972616b682a9a2a117e1aceb6b7eaac Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 07:36:32 +0200 Subject: [PATCH 11/23] Add missing check for required fields --- .../com/free2move/geoscala/jsoniter_scala.scala | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index 78c2f25..2accfb1 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -70,7 +70,7 @@ object jsoniter_scala { implicit val multiPolygonCodec: JsonValueCodec[MultiPolygon] = makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon.apply) - private def makeGeometryCodec[C: JsonValueCodec, G <: Geometry](`type`: String, coords: G => C, geom: C => G): JsonValueCodec[G] = + private def makeGeometryCodec[C: JsonValueCodec, G <: Geometry](tpe: String, coords: G => C, geom: C => G): JsonValueCodec[G] = new JsonValueCodec[G] { private val coordinatesCodec: JsonValueCodec[C] = implicitly[JsonValueCodec[C]] @@ -84,27 +84,33 @@ object jsoniter_scala { if (in.isCharBufEqualsTo(len, "type")) { if ((mask & 0x1) != 0) mask ^= 0x1 else in.duplicatedKeyError(len) - len = in.readStringAsCharBuf() - if (!in.isCharBufEqualsTo(len, `type`)) in.discriminatorValueError("type") + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), tpe)) in.discriminatorValueError("type") } else if (in.isCharBufEqualsTo(len, "coordinates")) { if ((mask & 0x2) != 0) mask ^= 0x2 else in.duplicatedKeyError(len) coordinates = coordinatesCodec.decodeValue(in, coordinates) } else in.skip() } + if (mask != 0) error(in, mask) geom(coordinates) } else in.readNullOrTokenError(default, '}') override def encodeValue(x: G, out: JsonWriter): Unit = { out.writeObjectStart() out.writeNonEscapedAsciiKey("type") - out.writeNonEscapedAsciiVal(`type`) + out.writeNonEscapedAsciiVal(tpe) out.writeNonEscapedAsciiKey("coordinates") coordinatesCodec.encodeValue(coords(x), out) out.writeObjectEnd() } override def nullValue: G = null.asInstanceOf[G] + + private def error(in: JsonReader, mask: Int): Nothing = + in.requiredFieldError { + if ((mask & 0x2) == 0) "type" + else "coordinates" + } } implicit val geometryCodec: JsonValueCodec[Geometry] = JsonCodecMaker.make From fb68d351aece1afb823160b5f9935b22e4cc4831 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 07:37:19 +0200 Subject: [PATCH 12/23] Fix formatting --- circe/src/main/scala/com/free2move/geoscala/circe.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circe/src/main/scala/com/free2move/geoscala/circe.scala b/circe/src/main/scala/com/free2move/geoscala/circe.scala index 1ee7fa5..a29ea67 100644 --- a/circe/src/main/scala/com/free2move/geoscala/circe.scala +++ b/circe/src/main/scala/com/free2move/geoscala/circe.scala @@ -132,7 +132,7 @@ trait GeoJsonDecoders { val typeCursor = cursor.downField("type") typeCursor.as[String] match { case Right(b) if b != tpe => Left(DecodingFailure(s"GeoJSON's type is not $tpe", typeCursor.history)) - case res => res + case res => res } } } From 9be4612c4a381784a92162962fe0dcdfa526d2d9 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 07:37:53 +0200 Subject: [PATCH 13/23] Update docs --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fec5ded..b2a3533 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,12 @@ [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.gnieh/geo-scala-core_2.13/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.gnieh/geo-scala-core_2.13) [![Continuous Integration](https://github.com/gnieh/geo-scala/actions/workflows/ci.yml/badge.svg)](https://github.com/gnieh/geo-scala/actions/workflows/ci.yml) -A core AST and utilities for GeoJSON ([RFC 7946][rfc-7946]) and more. Builds for Scala 3, 2.13 and 2.12 on JVM, JS and partially Scala Native. +A core AST and utilities for GeoJSON ([RFC 7946][rfc-7946]) and more. Builds for Scala 3, 2.13, and 2.12 on JVM, JS, and Scala Native. The project is divided in several submodules: - `core` contains the data model for geographical entities; - `circe` contains a set of [circe][circe] encoders and decoders for GeoJSON data model; + - `jsoniter-scala` contains a set of [jsoniter-scala][jsoniter-scala] codecs for GeoJSON data model; - `polyline` contains utilities to convert GeoJSON line strings to and from [polylines][polyline]. ## Quickstart @@ -42,4 +43,5 @@ Unless required by applicable law or agreed to in writing, software distributed [rfc-7946]: https://tools.ietf.org/html/rfc7946 [circe]: https://circe.github.io/circe +[jsoniter-scala]: https://github.com/plokhotnyuk/jsoniter-scala [polyline]: https://developers.google.com/maps/documentation/utilities/polylineutility From 2d1e068d97089aab1122333b6d3fa5fdfb3ee220 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 07:40:50 +0200 Subject: [PATCH 14/23] Clean up build --- build.sbt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/build.sbt b/build.sbt index af6b93c..74d1aba 100644 --- a/build.sbt +++ b/build.sbt @@ -49,8 +49,10 @@ lazy val circe = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( commonSettings ++ Seq( name := "geo-scala-circe", - libraryDependencies += "io.circe" %%% "circe-core" % circeVersion, - libraryDependencies += "io.circe" %%% "circe-parser" % circeVersion % Test + libraryDependencies ++= Seq( + "io.circe" %%% "circe-core" % circeVersion, + "io.circe" %%% "circe-parser" % circeVersion % Test + ) ) ) @@ -62,8 +64,10 @@ lazy val jsoniterScala = crossProject(JVMPlatform, JSPlatform, NativePlatform) .settings( commonSettings ++ Seq( name := "geo-scala-jsoniter-scala", - libraryDependencies += "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % jsoniterScalaVersion, - libraryDependencies += "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % jsoniterScalaVersion % Provided + libraryDependencies ++= Seq( + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-core" % jsoniterScalaVersion, + "com.github.plokhotnyuk.jsoniter-scala" %%% "jsoniter-scala-macros" % jsoniterScalaVersion % Provided + ) ) ) From 62919b4bbf5bfb82387780ac3c06e0209d9b6316 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 08:51:56 +0200 Subject: [PATCH 15/23] Add missing checking for `}` when parsing `Geometry` types --- .../src/main/scala/com/free2move/geoscala/jsoniter_scala.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index 2accfb1..d32144d 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -91,6 +91,7 @@ object jsoniter_scala { coordinates = coordinatesCodec.decodeValue(in, coordinates) } else in.skip() } + if (!in.isCurrentToken('}')) in.objectEndOrCommaError() if (mask != 0) error(in, mask) geom(coordinates) } else in.readNullOrTokenError(default, '}') From 2149dbafef39060e80054853f45a4f1ba3dcc48a Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Tue, 20 Sep 2022 13:56:20 +0200 Subject: [PATCH 16/23] Update Scala 2.13.x to 2.13.9 --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 74d1aba..59a5aa7 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ val scala212 = "2.12.17" -val scala213 = "2.13.8" +val scala213 = "2.13.9" val scala3 = "3.2.0" ThisBuild / tlBaseVersion := "0.3" From df773a94e59610d49612310f34e1312152ea3eaf Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 21 Sep 2022 10:42:47 +0200 Subject: [PATCH 17/23] Add missing tests for serialization and fix codecs to pass them --- .../scala/com/free2move/geoscala/circe.scala | 31 ++-- .../CirceDecodingAndEncodingTests.scala | 50 +++++++ .../geoscala/CirceDecodingTests.scala | 86 ----------- .../free2move/geoscala/jsoniter_scala.scala | 141 +++++++++++++++--- .../geoscala/JsoniterScalaCodecTests.scala | 57 +++++++ .../geoscala/JsoniterScalaDecodingTests.scala | 87 ----------- 6 files changed, 248 insertions(+), 204 deletions(-) create mode 100644 circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala delete mode 100644 circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala create mode 100644 jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala delete mode 100644 jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala diff --git a/circe/src/main/scala/com/free2move/geoscala/circe.scala b/circe/src/main/scala/com/free2move/geoscala/circe.scala index a29ea67..f5c5d79 100644 --- a/circe/src/main/scala/com/free2move/geoscala/circe.scala +++ b/circe/src/main/scala/com/free2move/geoscala/circe.scala @@ -21,13 +21,14 @@ import io.circe._ import io.circe.syntax._ trait LowPriorityGeoJsonEncoders { - implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { coord => + implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { coord: Coordinate => Json.arr(Json.fromDoubleOrNull(coord.longitude), Json.fromDoubleOrNull(coord.latitude)) } - private def makeGeometryEncoder[C: Encoder, G <: Geometry](`type`: String, coords: G => C): Encoder[G] = Encoder.instance { geometry => - Json.obj("type" := `type`, "coordinates" := coords(geometry)) - } + private def makeGeometryEncoder[C: Encoder, G <: Geometry](`type`: String, coords: G => C): Encoder[G] = + Encoder.instance { geometry: G => + Json.obj("type" := `type`, "coordinates" := coords(geometry)) + } implicit val pointEncoder: Encoder[Point] = makeGeometryEncoder("Point", _.coordinates) @@ -53,18 +54,20 @@ trait GeoJsonEncoders extends LowPriorityGeoJsonEncoders { case mp: MultiPolygon => mp.asJson } - implicit def extendedFeatureEncoder[Properties: Encoder.AsObject]: Encoder[Feature[Properties]] = Encoder.instance { feature => - Json.obj("type" := "Feature", "properties" := feature.properties, "geometry" := feature.geometry) - } + implicit def extendedFeatureEncoder[Properties: Encoder]: Encoder[Feature[Properties]] = + Encoder.instance { feature: Feature[Properties] => + Json.obj("type" := "Feature", "properties" := feature.properties, "geometry" := feature.geometry) + } - implicit def extendedFeatureCollectionEncoder[Properties: Encoder.AsObject]: Encoder[FeatureCollection[Properties]] = Encoder.instance { featureCollection => - Json.obj("type" := "FeatureCollection", "features" := featureCollection.features) - } + implicit def extendedFeatureCollectionEncoder[Properties: Encoder]: Encoder[FeatureCollection[Properties]] = + Encoder.instance { featureCollection: FeatureCollection[Properties] => + Json.obj("type" := "FeatureCollection", "features" := featureCollection.features) + } - implicit def geojsonEncoder[Properties: Encoder.AsObject]: Encoder[GeoJson[Properties]] = Encoder.instance { - case fc @ FeatureCollection(_) => fc.asJson - case f @ Feature(_, _) => f.asJson - case geom: Geometry => (geom: Geometry).asJson + implicit def geojsonEncoder[Properties: Encoder]: Encoder[GeoJson[Properties]] = Encoder.instance { + case fc: FeatureCollection[Properties] => fc.asJson + case f: Feature[Properties] => f.asJson + case geom: Geometry => (geom: Geometry).asJson } } diff --git a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala new file mode 100644 index 0000000..fd3fcd6 --- /dev/null +++ b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingAndEncodingTests.scala @@ -0,0 +1,50 @@ +/* + * Copyright 2019 GHM Mobile Development GmbH + * + * 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 com.free2move.geoscala + +import com.free2move.geoscala.circe._ +import io.circe._ +import io.circe.syntax._ +import org.scalatest.EitherValues +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class CirceDecodingAndEncodingTests extends AnyFlatSpec with Matchers with EitherValues { + "The circe decoders" should "parse and serialize simple 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + parser.decode[Point](json) shouldBe Right(obj) + parser.decode[Geometry](json) shouldBe Right(obj) + Printer.noSpaces.print(obj.asJson) shouldBe json + } + + it should "parse points with more dimensions as 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775,7.000,42.12345]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + parser.decode[Point](json) shouldBe Right(obj) + parser.decode[Geometry](json) shouldBe Right(obj) + } + + it should "parse and serialize FeatureCollection using Json encoder and decoder" in { + val json = + """{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":7},"geometry":{"type":"Point","coordinates":[12.3046875,51.8357775]}}]}""" + val obj = FeatureCollection(List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775))))) + parser.decode[FeatureCollection[Json]](json) shouldBe Right(obj) + parser.decode[GeoJson[Json]](json) shouldBe Right(obj) + Printer.noSpaces.print(obj.asJson) shouldBe json + } +} diff --git a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala b/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala deleted file mode 100644 index 439c891..0000000 --- a/circe/src/test/scala/com/free2move/geoscala/CirceDecodingTests.scala +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2019 GHM Mobile Development GmbH - * - * 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 com.free2move.geoscala - -import com.free2move.geoscala.circe._ -import io.circe._ -import io.circe.syntax._ -import org.scalatest.EitherValues -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class CirceDecodingTests extends AnyFlatSpec with Matchers with EitherValues { - "The circe decoders" should "handle simple 2D points" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - }""" - parser.decode[Point](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - parser.decode[Geometry](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - } - - it should "handle points with more dimensions" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775, - 7.000, - 42.12345 - ] - }""" - parser.decode[Point](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - parser.decode[Geometry](json) shouldBe Right(Point(Coordinate(12.3046875, 51.8357775))) - } - - it should "handle FeatureCollection without Properties as pure JSON correctly" in { - val json = - """{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "id": 7 - }, - "geometry": { - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - } - } - ] - }""" - parser.decode[FeatureCollection[Json]](json) shouldBe Right( - FeatureCollection( - List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - ) - parser.decode[GeoJson[Json]](json) shouldBe Right( - FeatureCollection( - List(Feature(Json.obj("id" := 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - ) - } -} diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index d32144d..b18adf7 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -18,7 +18,7 @@ package com.free2move.geoscala import com.github.plokhotnyuk.jsoniter_scala.core._ import com.github.plokhotnyuk.jsoniter_scala.macros._ -import scala.annotation.nowarn +import scala.collection.mutable.ListBuffer object jsoniter_scala { // Uncomment for printing of codecs generated by macros @@ -33,7 +33,7 @@ object jsoniter_scala { val lat = in.readDouble() while (in.isNextToken(',')) in.skip() if (!in.isCurrentToken(']')) in.arrayEndOrCommaError() - Coordinate(lon, lat) + new Coordinate(lon, lat) } else in.readNullOrTokenError(default, '[') override def encodeValue(x: Coordinate, out: JsonWriter): Unit = { @@ -70,9 +70,10 @@ object jsoniter_scala { implicit val multiPolygonCodec: JsonValueCodec[MultiPolygon] = makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon.apply) - private def makeGeometryCodec[C: JsonValueCodec, G <: Geometry](tpe: String, coords: G => C, geom: C => G): JsonValueCodec[G] = + private[this] def makeGeometryCodec[C, G <: Geometry](tpe: String, coords: G => C, geom: C => G) + (implicit coordinatesCodec: JsonValueCodec[C]): JsonValueCodec[G] = new JsonValueCodec[G] { - private val coordinatesCodec: JsonValueCodec[C] = implicitly[JsonValueCodec[C]] + override val nullValue: G = null.asInstanceOf[G] override def decodeValue(in: JsonReader, default: G): G = if (in.isNextToken('{')) { @@ -84,7 +85,9 @@ object jsoniter_scala { if (in.isCharBufEqualsTo(len, "type")) { if ((mask & 0x1) != 0) mask ^= 0x1 else in.duplicatedKeyError(len) - if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), tpe)) in.discriminatorValueError("type") + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), tpe)) { + in.discriminatorValueError("type") + } } else if (in.isCharBufEqualsTo(len, "coordinates")) { if ((mask & 0x2) != 0) mask ^= 0x2 else in.duplicatedKeyError(len) @@ -105,27 +108,133 @@ object jsoniter_scala { out.writeObjectEnd() } - override def nullValue: G = null.asInstanceOf[G] - - private def error(in: JsonReader, mask: Int): Nothing = + private[this] def error(in: JsonReader, mask: Int): Nothing = in.requiredFieldError { - if ((mask & 0x2) == 0) "type" + if ((mask & 0x1) != 0) "type" else "coordinates" } } implicit val geometryCodec: JsonValueCodec[Geometry] = JsonCodecMaker.make - @nowarn - implicit def featureCodec[P: JsonValueCodec]: JsonValueCodec[Feature[P]] = JsonCodecMaker.make + implicit def featureCodec[P](implicit propertiesCodec: JsonValueCodec[P]): JsonValueCodec[Feature[P]] = + new JsonValueCodec[Feature[P]] { + override val nullValue: Feature[P] = null.asInstanceOf[Feature[P]] + + override def decodeValue(in: JsonReader, default: Feature[P]): Feature[P] = + if (in.isNextToken('{')) { + var properties: P = propertiesCodec.nullValue + var geometry: Geometry = geometryCodec.nullValue + var mask = 7 + var len = -1 + while (len < 0 || in.isNextToken(',')) { + len = in.readKeyAsCharBuf() + if (in.isCharBufEqualsTo(len, "type")) { + if ((mask & 0x1) != 0) mask ^= 0x1 + else in.duplicatedKeyError(len) + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), "Feature")) { + in.discriminatorValueError("type") + } + } else if (in.isCharBufEqualsTo(len, "properties")) { + if ((mask & 0x2) != 0) mask ^= 0x2 + else in.duplicatedKeyError(len) + properties = propertiesCodec.decodeValue(in, properties) + } else if (in.isCharBufEqualsTo(len, "geometry")) { + if ((mask & 0x4) != 0) mask ^= 0x4 + else in.duplicatedKeyError(len) + geometry = geometryCodec.decodeValue(in, geometry) + } else in.skip() + } + if (!in.isCurrentToken('}')) in.objectEndOrCommaError() + if (mask != 0) error(in, mask) + new Feature(properties, geometry) + } else in.readNullOrTokenError(default, '}') + + override def encodeValue(x: Feature[P], out: JsonWriter): Unit = { + out.writeObjectStart() + out.writeNonEscapedAsciiKey("type") + out.writeNonEscapedAsciiVal("Feature") + out.writeNonEscapedAsciiKey("properties") + propertiesCodec.encodeValue(x.properties, out) + out.writeNonEscapedAsciiKey("geometry") + geometryCodec.encodeValue(x.geometry, out) + out.writeObjectEnd() + } + + private[this] def error(in: JsonReader, mask: Int): Nothing = + in.requiredFieldError { + if ((mask & 0x1) != 0) "type" + else if ((mask & 0x2) != 0) "properties" + else "geometry" + } + } - @nowarn - implicit def featureCollectionCodec[P: JsonValueCodec]: JsonValueCodec[FeatureCollection[P]] = JsonCodecMaker.make + implicit def featureCollectionCodec[P](implicit featureCodec: JsonValueCodec[Feature[P]]): JsonValueCodec[FeatureCollection[P]] = + new JsonValueCodec[FeatureCollection[P]] { + override val nullValue: FeatureCollection[P] = null.asInstanceOf[FeatureCollection[P]] + + override def decodeValue(in: JsonReader, default: FeatureCollection[P]): FeatureCollection[P] = + if (in.isNextToken('{')) { + var features: List[Feature[P]] = Nil + var mask = 3 + var len = -1 + while (len < 0 || in.isNextToken(',')) { + len = in.readKeyAsCharBuf() + if (in.isCharBufEqualsTo(len, "type")) { + if ((mask & 0x1) != 0) mask ^= 0x1 + else in.duplicatedKeyError(len) + if (!in.isCharBufEqualsTo(in.readStringAsCharBuf(), "FeatureCollection")) { + in.discriminatorValueError("type") + } + } else if (in.isCharBufEqualsTo(len, "features")) { + if ((mask & 0x2) != 0) mask ^= 0x2 + else in.duplicatedKeyError(len) + if (in.isNextToken('[')) { + if (!in.isNextToken(']')) { + in.rollbackToken() + val buf = new ListBuffer[Feature[P]] + while ({ + buf.addOne(featureCodec.decodeValue(in, featureCodec.nullValue)) + in.isNextToken(',') + }) () + if (in.isCurrentToken(']')) features = buf.toList + else in.arrayEndOrCommaError() + } else in.readNullOrTokenError(features, '[') + } + } else in.skip() + } + if (!in.isCurrentToken('}')) in.objectEndOrCommaError() + if (mask != 0) error(in, mask) + new FeatureCollection(features) + } else in.readNullOrTokenError(default, '}') + + override def encodeValue(x: FeatureCollection[P], out: JsonWriter): Unit = { + out.writeObjectStart() + out.writeNonEscapedAsciiKey("type") + out.writeNonEscapedAsciiVal("FeatureCollection") + out.writeNonEscapedAsciiKey("features") + out.writeArrayStart() + var remainingFeatures = x.features + while (remainingFeatures ne Nil) { + featureCodec.encodeValue(remainingFeatures.head, out) + remainingFeatures = remainingFeatures.tail + } + out.writeArrayEnd() + out.writeObjectEnd() + } + + private[this] def error(in: JsonReader, mask: Int): Nothing = + in.requiredFieldError { + if ((mask & 0x1) != 0) "type" + else "features" + } + } implicit def geoJson[P: JsonValueCodec]: JsonValueCodec[GeoJson[P]] = new JsonValueCodec[GeoJson[P]] { - private val fc: JsonValueCodec[Feature[P]] = featureCodec - private val fcc: JsonValueCodec[FeatureCollection[P]] = featureCollectionCodec + private[this] val fc: JsonValueCodec[Feature[P]] = featureCodec + private[this] val fcc: JsonValueCodec[FeatureCollection[P]] = featureCollectionCodec + override val nullValue: GeoJson[P] = null.asInstanceOf[GeoJson[P]] override def decodeValue(in: JsonReader, default: GeoJson[P]): GeoJson[P] = { in.setMark() @@ -150,7 +259,5 @@ object jsoniter_scala { case fc: FeatureCollection[P] => fcc.encodeValue(fc, out) case _ => geometryCodec.encodeValue(x.asInstanceOf[Geometry], out) } - - override def nullValue: GeoJson[P] = null.asInstanceOf[GeoJson[P]] } } diff --git a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala new file mode 100644 index 0000000..4e78bb0 --- /dev/null +++ b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaCodecTests.scala @@ -0,0 +1,57 @@ +/* + * Copyright 2019 GHM Mobile Development GmbH + * + * 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 com.free2move.geoscala + +import com.free2move.geoscala.jsoniter_scala._ +import com.github.plokhotnyuk.jsoniter_scala.core._ +import com.github.plokhotnyuk.jsoniter_scala.macros._ +import org.scalatest.EitherValues +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class JsoniterScalaDecodingAndEncodingTests extends AnyFlatSpec with Matchers with EitherValues { + "The jsoniter-scala codecs" should "parse and serialize simple 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + readFromString[Point](json) shouldBe obj + readFromString[Geometry](json) shouldBe obj + writeToString[Point](obj) shouldBe json + writeToString[Geometry](obj) shouldBe json + } + + it should "parse points with more dimensions as 2D points" in { + val json = """{"type":"Point","coordinates":[12.3046875,51.8357775,7.000,42.12345]}""" + val obj = Point(Coordinate(12.3046875, 51.8357775)) + readFromString[Point](json) shouldBe obj + readFromString[Geometry](json) shouldBe obj + } + + it should "pare and serialize FeatureCollection using provided codec for properties" in { + type Json = Map[String, Int] + + implicit val jsonCodec: JsonValueCodec[Json] = JsonCodecMaker.make + + val json = + """{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":7},"geometry":{"type":"Point","coordinates":[12.3046875,51.8357775]}}]}""" + val obj = + FeatureCollection(List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775))))) + readFromString[FeatureCollection[Json]](json) shouldBe obj + readFromString[GeoJson[Json]](json) shouldBe obj + writeToString[FeatureCollection[Json]](obj) shouldBe json + writeToString[GeoJson[Json]](obj) shouldBe json + } +} diff --git a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala b/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala deleted file mode 100644 index c1c7519..0000000 --- a/jsoniter-scala/src/test/scala/com/free2move/geoscala/JsoniterScalaDecodingTests.scala +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2019 GHM Mobile Development GmbH - * - * 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 com.free2move.geoscala - -import com.free2move.geoscala.jsoniter_scala._ -import com.github.plokhotnyuk.jsoniter_scala.core._ -import com.github.plokhotnyuk.jsoniter_scala.macros._ -import org.scalatest.EitherValues -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class JsoniterScalaDecodingTests extends AnyFlatSpec with Matchers with EitherValues { - "The jsoniter-scala codecs" should "handle simple 2D points" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - }""" - readFromString[Point](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - readFromString[Geometry](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - } - - it should "handle points with more dimensions" in { - val json = - """{ - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775, - 7.000, - 42.12345 - ] - }""" - readFromString[Point](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - readFromString[Geometry](json) shouldBe Point(Coordinate(12.3046875, 51.8357775)) - } - - it should "handle FeatureCollection without Properties as pure JSON correctly" in { - type Json = Map[String, Int] - - implicit val jsonCodec: JsonValueCodec[Json] = JsonCodecMaker.make - - val json = - """{ - "type": "FeatureCollection", - "features": [ - { - "type": "Feature", - "properties": { - "id": 7 - }, - "geometry": { - "type": "Point", - "coordinates": [ - 12.3046875, - 51.8357775 - ] - } - } - ] - }""" - readFromString[FeatureCollection[Json]](json) shouldBe FeatureCollection( - List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - readFromString[GeoJson[Json]](json) shouldBe FeatureCollection( - List(Feature(Map("id" -> 7), Point(Coordinate(12.3046875, 51.8357775)))) - ) - } - -} From 1d684ee2555a2566cd0ad5714aef15314d68f3cd Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 21 Sep 2022 10:53:21 +0200 Subject: [PATCH 18/23] Fix formatting --- .../main/scala/com/free2move/geoscala/jsoniter_scala.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index b18adf7..3e85ec4 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -70,8 +70,9 @@ object jsoniter_scala { implicit val multiPolygonCodec: JsonValueCodec[MultiPolygon] = makeGeometryCodec("MultiPolygon", _.coordinates, MultiPolygon.apply) - private[this] def makeGeometryCodec[C, G <: Geometry](tpe: String, coords: G => C, geom: C => G) - (implicit coordinatesCodec: JsonValueCodec[C]): JsonValueCodec[G] = + private[this] def makeGeometryCodec[C, G <: Geometry](tpe: String, coords: G => C, geom: C => G)(implicit + coordinatesCodec: JsonValueCodec[C] + ): JsonValueCodec[G] = new JsonValueCodec[G] { override val nullValue: G = null.asInstanceOf[G] From 300655b3e124766cc3f98e955168985a701d6144 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 28 Sep 2022 10:21:17 +0200 Subject: [PATCH 19/23] Fix compilation error with Scala 2.12.x --- .../src/main/scala/com/free2move/geoscala/jsoniter_scala.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala index 3e85ec4..a0e9f1e 100644 --- a/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala +++ b/jsoniter-scala/src/main/scala/com/free2move/geoscala/jsoniter_scala.scala @@ -195,7 +195,7 @@ object jsoniter_scala { in.rollbackToken() val buf = new ListBuffer[Feature[P]] while ({ - buf.addOne(featureCodec.decodeValue(in, featureCodec.nullValue)) + buf += featureCodec.decodeValue(in, featureCodec.nullValue) in.isNextToken(',') }) () if (in.isCurrentToken(']')) features = buf.toList From 677d772642260409817aaf7fde8cdb3774a8e3e8 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 28 Sep 2022 10:28:22 +0200 Subject: [PATCH 20/23] Fix Scala 3 compiler warnings --- .../scala/com/free2move/geoscala/circe.scala | 49 ++++++++++--------- 1 file changed, 26 insertions(+), 23 deletions(-) diff --git a/circe/src/main/scala/com/free2move/geoscala/circe.scala b/circe/src/main/scala/com/free2move/geoscala/circe.scala index f5c5d79..677a331 100644 --- a/circe/src/main/scala/com/free2move/geoscala/circe.scala +++ b/circe/src/main/scala/com/free2move/geoscala/circe.scala @@ -21,12 +21,12 @@ import io.circe._ import io.circe.syntax._ trait LowPriorityGeoJsonEncoders { - implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { coord: Coordinate => + implicit val coordinateEncoder: Encoder[Coordinate] = Encoder.instance { (coord: Coordinate) => Json.arr(Json.fromDoubleOrNull(coord.longitude), Json.fromDoubleOrNull(coord.latitude)) } private def makeGeometryEncoder[C: Encoder, G <: Geometry](`type`: String, coords: G => C): Encoder[G] = - Encoder.instance { geometry: G => + Encoder.instance { (geometry: G) => Json.obj("type" := `type`, "coordinates" := coords(geometry)) } @@ -55,12 +55,12 @@ trait GeoJsonEncoders extends LowPriorityGeoJsonEncoders { } implicit def extendedFeatureEncoder[Properties: Encoder]: Encoder[Feature[Properties]] = - Encoder.instance { feature: Feature[Properties] => + Encoder.instance { (feature: Feature[Properties]) => Json.obj("type" := "Feature", "properties" := feature.properties, "geometry" := feature.geometry) } implicit def extendedFeatureCollectionEncoder[Properties: Encoder]: Encoder[FeatureCollection[Properties]] = - Encoder.instance { featureCollection: FeatureCollection[Properties] => + Encoder.instance { (featureCollection: FeatureCollection[Properties]) => Json.obj("type" := "FeatureCollection", "features" := featureCollection.features) } @@ -80,12 +80,13 @@ trait GeoJsonDecoders { } @inline - private def makeGeometryDecoder[C: Decoder, G <: Geometry](`type`: String, create: C => G): Decoder[G] = Decoder.instance[G] { cursor => - for { - _ <- ensureType(cursor, `type`) - coords <- cursor.downField("coordinates").as[C] - } yield create(coords) - } + private def makeGeometryDecoder[C: Decoder, G <: Geometry](`type`: String, create: C => G): Decoder[G] = + Decoder.instance[G] { (cursor: HCursor) => + for { + _ <- ensureType(cursor, `type`) + coords <- cursor.downField("coordinates").as[C] + } yield create(coords) + } implicit val pointDecoder: Decoder[Point] = makeGeometryDecoder("Point", Point.apply) @@ -115,20 +116,22 @@ trait GeoJsonDecoders { geometryDecoder.widen[GeoJson[Nothing]].asInstanceOf[Decoder[GeoJson[Properties]]] ).reduce(_ or _) - implicit def extendedFeatureDecoder[Properties: Decoder]: Decoder[Feature[Properties]] = Decoder.instance { cursor => - for { - _ <- ensureType(cursor, "Feature") - properties <- cursor.downField("properties").as[Properties] - geometry <- cursor.downField("geometry").as[Geometry] - } yield Feature(properties, geometry) - } + implicit def extendedFeatureDecoder[Properties: Decoder]: Decoder[Feature[Properties]] = + Decoder.instance { (cursor: HCursor) => + for { + _ <- ensureType(cursor, "Feature") + properties <- cursor.downField("properties").as[Properties] + geometry <- cursor.downField("geometry").as[Geometry] + } yield Feature(properties, geometry) + } - implicit def extendedFeatureCollectionDecoder[Properties: Decoder]: Decoder[FeatureCollection[Properties]] = Decoder.instance { cursor => - for { - _ <- ensureType(cursor, "FeatureCollection") - features <- cursor.downField("features").as[List[Feature[Properties]]] - } yield FeatureCollection(features) - } + implicit def extendedFeatureCollectionDecoder[Properties: Decoder]: Decoder[FeatureCollection[Properties]] = + Decoder.instance { (cursor: HCursor) => + for { + _ <- ensureType(cursor, "FeatureCollection") + features <- cursor.downField("features").as[List[Feature[Properties]]] + } yield FeatureCollection(features) + } @inline private def ensureType(cursor: HCursor, tpe: String): Decoder.Result[String] = { From 9463a02c42a5f627b7c6510d0c2b794dcfad8535 Mon Sep 17 00:00:00 2001 From: Andriy Plokhotnyuk Date: Wed, 28 Sep 2022 14:25:43 +0200 Subject: [PATCH 21/23] Bump the base version and turn on binary compatibility checks --- build.sbt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/build.sbt b/build.sbt index 59a5aa7..226c896 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val scala212 = "2.12.17" val scala213 = "2.13.9" val scala3 = "3.2.0" -ThisBuild / tlBaseVersion := "0.3" +ThisBuild / tlBaseVersion := "0.4" ThisBuild / organization := "org.gnieh" ThisBuild / organizationName := "GHM Mobile Development GmbH" @@ -26,10 +26,7 @@ lazy val commonSettings = Seq( "org.scalatest" %%% "scalatest" % "3.2.13" % Test, "org.scalatestplus" %%% "scalacheck-1-16" % "3.2.13.0" % Test, "org.scalacheck" %%% "scalacheck" % "1.16.0" % Test - ), - // disable MiMa until we have proper version released on gnieh / all platforms - // once removed, the tlBaseVersion has to adjusted as well - mimaPreviousArtifacts := Set.empty + ) ) lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform) From 2a32e3314b67d26ad6fd657367a1f3d95cea2898 Mon Sep 17 00:00:00 2001 From: Yannick Heiber Date: Fri, 30 Sep 2022 11:36:38 +0200 Subject: [PATCH 22/23] Fix Scala version in workflows --- .github/workflows/ci.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c8d71b6..48aa7d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.17, 2.13.8, 3.2.0] + scala: [2.12.17, 2.13.9, 3.2.0] java: [temurin@11] project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} @@ -114,7 +114,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.8] + scala: [2.13.9] java: [temurin@11] runs-on: ${{ matrix.os }} steps: @@ -181,32 +181,32 @@ jobs: tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.8, rootJS) + - name: Download target directories (2.13.9, rootJS) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootJS - - name: Inflate target directories (2.13.8, rootJS) + - name: Inflate target directories (2.13.9, rootJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.8, rootJVM) + - name: Download target directories (2.13.9, rootJVM) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootJVM - - name: Inflate target directories (2.13.8, rootJVM) + - name: Inflate target directories (2.13.9, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.8, rootNative) + - name: Download target directories (2.13.9, rootNative) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootNative - - name: Inflate target directories (2.13.8, rootNative) + - name: Inflate target directories (2.13.9, rootNative) run: | tar xf targets.tar rm targets.tar From a3998ed2c3ef064265accc3b1d75ab26402d36c3 Mon Sep 17 00:00:00 2001 From: Yannick Heiber Date: Fri, 30 Sep 2022 11:45:36 +0200 Subject: [PATCH 23/23] Downgrade to Scala 2.13.8 --- .github/workflows/ci.yml | 22 +++++++++++----------- build.sbt | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48aa7d1..c8d71b6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.12.17, 2.13.9, 3.2.0] + scala: [2.12.17, 2.13.8, 3.2.0] java: [temurin@11] project: [rootJS, rootJVM, rootNative] runs-on: ${{ matrix.os }} @@ -114,7 +114,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - scala: [2.13.9] + scala: [2.13.8] java: [temurin@11] runs-on: ${{ matrix.os }} steps: @@ -181,32 +181,32 @@ jobs: tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.9, rootJS) + - name: Download target directories (2.13.8, rootJS) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootJS + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJS - - name: Inflate target directories (2.13.9, rootJS) + - name: Inflate target directories (2.13.8, rootJS) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.9, rootJVM) + - name: Download target directories (2.13.8, rootJVM) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootJVM + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootJVM - - name: Inflate target directories (2.13.9, rootJVM) + - name: Inflate target directories (2.13.8, rootJVM) run: | tar xf targets.tar rm targets.tar - - name: Download target directories (2.13.9, rootNative) + - name: Download target directories (2.13.8, rootNative) uses: actions/download-artifact@v2 with: - name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.9-rootNative + name: target-${{ matrix.os }}-${{ matrix.java }}-2.13.8-rootNative - - name: Inflate target directories (2.13.9, rootNative) + - name: Inflate target directories (2.13.8, rootNative) run: | tar xf targets.tar rm targets.tar diff --git a/build.sbt b/build.sbt index 226c896..cc77a66 100644 --- a/build.sbt +++ b/build.sbt @@ -1,5 +1,5 @@ val scala212 = "2.12.17" -val scala213 = "2.13.9" +val scala213 = "2.13.8" val scala3 = "3.2.0" ThisBuild / tlBaseVersion := "0.4"