From 9096603f935a2b44cae891328fb4c4a3d81921df Mon Sep 17 00:00:00 2001 From: Jan Van den bosch Date: Wed, 28 Aug 2019 19:07:54 +0200 Subject: [PATCH] Depend on fixed proj4 (2.3 branch) (#3048) * Depend on fixed proj4 (2.3 branch) Signed-off-by: Jan Van den bosch --- .../main/scala/geotrellis/bench/package.scala | 16 +++ .../scala/geotrellis/proj4/CRSBench.scala | 116 ++++++++++++++++++ .../raster/GenericRasterBench.scala | 1 + .../raster/render/RenderBench.scala | 1 + .../reproject/RasterizingReprojectBench.scala | 16 +++ build.sbt | 7 +- docs/CHANGELOG.rst | 3 +- .../src/main/scala/geotrellis/proj4/CRS.scala | 108 +++------------- .../scala/geotrellis/proj4/ConusAlbers.scala | 2 +- .../main/scala/geotrellis/proj4/LatLng.scala | 2 +- .../scala/geotrellis/proj4/WebMercator.scala | 2 +- project/Dependencies.scala | 2 +- project/Settings.scala | 15 +-- project/Version.scala | 2 +- .../io/geotiff/reader/GeoTiffReaderSpec.scala | 3 +- 15 files changed, 185 insertions(+), 111 deletions(-) create mode 100644 bench/src/main/scala/geotrellis/proj4/CRSBench.scala diff --git a/bench/src/main/scala/geotrellis/bench/package.scala b/bench/src/main/scala/geotrellis/bench/package.scala index df2207f242..f7be8d2a1c 100644 --- a/bench/src/main/scala/geotrellis/bench/package.scala +++ b/bench/src/main/scala/geotrellis/bench/package.scala @@ -1,3 +1,19 @@ +/* + * 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 import geotrellis.raster.io.geotiff.SinglebandGeoTiff diff --git a/bench/src/main/scala/geotrellis/proj4/CRSBench.scala b/bench/src/main/scala/geotrellis/proj4/CRSBench.scala new file mode 100644 index 0000000000..336e537ca6 --- /dev/null +++ b/bench/src/main/scala/geotrellis/proj4/CRSBench.scala @@ -0,0 +1,116 @@ +/* + * Copyright 2019 Azavea & Astraea, Inc. + * + * 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. + * + * + * This file is a bit modified version of https://github.com/locationtech/rasterframes/blob/dc561dc9ad49eca043a6a0465f11d18d09566db1/bench/src/main/scala/org/locationtech/rasterframes/bench/CRSBench.scala + */ + +package geotrellis.proj4 + +import java.util.concurrent.TimeUnit + +import org.locationtech.proj4j.CoordinateReferenceSystem +import org.openjdk.jmh.annotations._ + +@BenchmarkMode(Array(Mode.AverageTime)) +@State(Scope.Benchmark) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +class CRSBench { + + var crs1: CRS = _ + var crs2: CRS = _ + + /** + * jmh:run -i 3 -wi 3 -f1 -t1 .*CRSBench.* + * + * LazyCRS (rasterframes fix): + * [info] Benchmark Mode Cnt Score Error Units + * [info] CRSBench.logicalEqualsFalse avgt 3 13.534 ± 6.396 us/op + * [info] CRSBench.logicalEqualsTrue avgt 3 0.254 ± 0.116 us/op + * [info] CRSBench.logicalLazyEqualsFalse avgt 3 14.505 ± 10.106 us/op + * [info] CRSBench.logicalLazyEqualsTrue avgt 3 0.035 ± 0.007 us/op + * [info] CRSBench.resolveCRS avgt 3 0.069 ± 0.082 us/op + * + * Proj4 1.0.0: + * [info] Benchmark Mode Cnt Score Error Units + * [info] CRSBench.logicalEqualsFalse avgt 3 23.397 ± 37.290 us/op + * [info] CRSBench.logicalEqualsTrue avgt 3 11.639 ± 49.851 us/op + * [info] CRSBench.logicalLazyEqualsFalse avgt 3 27.671 ± 106.851 us/op + * [info] CRSBench.logicalLazyEqualsTrue avgt 3 12.928 ± 28.639 us/op + * [info] CRSBench.resolveCRS avgt 3 0.233 ± 0.844 us/op + * + * GeoTools: + * [info] Benchmark Mode Cnt Score Error Units + * [info] CRSBench.logicalEqualsFalse avgt 3 0.046 ± 0.068 us/op + * [info] CRSBench.logicalEqualsTrue avgt 3 0.042 ± 0.043 us/op + * [info] CRSBench.logicalLazyEqualsFalse avgt 3 0.047 ± 0.052 us/op + * [info] CRSBench.logicalLazyEqualsTrue avgt 3 0.042 ± 0.028 us/op + * [info] CRSBench.resolveCRS avgt 3 0.041 ± 0.127 us/op + * + * Proj4 1.0.1 (backed by Caffeine, unbounded): + * [info] Benchmark Mode Cnt Score Error Units + * [info] CRSBench.epsgCodeReadTest avgt 3 0.062 ± 0.032 us/op + * [info] CRSBench.logicalEqualsFalse avgt 3 0.037 ± 0.009 us/op + * [info] CRSBench.logicalEqualsTrue avgt 3 0.054 ± 0.013 us/op + * [info] CRSBench.logicalVarEqualsFalse avgt 3 0.038 ± 0.015 us/op + * [info] CRSBench.logicalVarEqualsTrue avgt 3 0.053 ± 0.022 us/op + * [info] CRSBench.resolveCRS avgt 3 0.035 ± 0.025 us/op + * + * Proj4 1.0.1 (backed by ConcurrentHashMap): + * [info] Benchmark Mode Cnt Score Error Units + * [info] CRSBench.getEpsgCodeTest avgt 3 0.064 ± 0.161 us/op + * [info] CRSBench.logicalEqualsFalse avgt 3 0.039 ± 0.009 us/op + * [info] CRSBench.logicalEqualsTrue avgt 3 0.035 ± 0.020 us/op + * [info] CRSBench.logicalVarEqualsFalse avgt 3 0.040 ± 0.068 us/op + * [info] CRSBench.logicalVarEqualsTrue avgt 3 0.037 ± 0.034 us/op + * [info] CRSBench.resolveCRS avgt 3 0.034 ± 0.008 us/op + */ + + @Setup(Level.Invocation) + def setupData(): Unit = { + crs1 = CRS.fromEpsgCode(4326) + crs2 = WebMercator + } + + @Benchmark + def resolveCRS(): CoordinateReferenceSystem = { + crs1.proj4jCrs + } + + @Benchmark + def logicalEqualsTrue(): Boolean = { + crs1 == LatLng + } + + @Benchmark + def logicalEqualsFalse(): Boolean = { + crs1 == WebMercator + } + + @Benchmark + def logicalVarEqualsTrue(): Boolean = { + crs1 == crs1 + } + + @Benchmark + def logicalVarEqualsFalse(): Boolean = { + crs1 == crs2 + } + + @Benchmark + def getEpsgCodeTest(): Boolean = { + CRS.getEpsgCode("+proj=longlat +ellps=intl +towgs84=84,274,65,0,0,0,0 +no_defs").get == 4630 + } +} diff --git a/bench/src/main/scala/geotrellis/raster/GenericRasterBench.scala b/bench/src/main/scala/geotrellis/raster/GenericRasterBench.scala index d04bbf7ab4..8cc6acd7ec 100644 --- a/bench/src/main/scala/geotrellis/raster/GenericRasterBench.scala +++ b/bench/src/main/scala/geotrellis/raster/GenericRasterBench.scala @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package geotrellis.raster import geotrellis.bench.init diff --git a/bench/src/main/scala/geotrellis/raster/render/RenderBench.scala b/bench/src/main/scala/geotrellis/raster/render/RenderBench.scala index 30df2b495d..13bb635e64 100644 --- a/bench/src/main/scala/geotrellis/raster/render/RenderBench.scala +++ b/bench/src/main/scala/geotrellis/raster/render/RenderBench.scala @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package geotrellis.raster.render import org.openjdk.jmh.annotations._ diff --git a/bench/src/main/scala/geotrellis/raster/reproject/RasterizingReprojectBench.scala b/bench/src/main/scala/geotrellis/raster/reproject/RasterizingReprojectBench.scala index f6c9e42b7e..b1814d80c2 100644 --- a/bench/src/main/scala/geotrellis/raster/reproject/RasterizingReprojectBench.scala +++ b/bench/src/main/scala/geotrellis/raster/reproject/RasterizingReprojectBench.scala @@ -1,3 +1,19 @@ +/* + * 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.raster.reproject import org.openjdk.jmh.annotations._ diff --git a/build.sbt b/build.sbt index 642149ab07..e1b154a1bf 100644 --- a/build.sbt +++ b/build.sbt @@ -77,8 +77,11 @@ lazy val commonSettings = Seq( shellPrompt := { s => Project.extract(s).currentProject.id + " > " }, resolvers ++= Seq( - "geosolutions" at "http://maven.geo-solutions.it/", - "osgeo" at "http://download.osgeo.org/webdav/geotools/" + Resolver.mavenLocal, + Settings.Repositories.geosolutions, + Settings.Repositories.osgeo, + Settings.Repositories.locationtechReleases, + Settings.Repositories.locationtechSnapshots ), headerLicense := Some(HeaderLicense.ALv2("2018", "Azavea")), // preserve year of old headers diff --git a/docs/CHANGELOG.rst b/docs/CHANGELOG.rst index 98147718c0..733c3eb55e 100644 --- a/docs/CHANGELOG.rst +++ b/docs/CHANGELOG.rst @@ -19,6 +19,7 @@ Fixes & Updates - Get the static DefaultAWSCredentialsProviderChain instance rather than constructing a new one every time (`#2895 `_). - Fix for improper handling of values in `ArrayTile.combine{Double}`'s default case (`#2908 `_). +- Bump proj4 version to fix multiple performance issues (`#3039 `_). 2.3.0 ----- @@ -36,7 +37,7 @@ Fixes & Updates ----- *2019 Jan 11* -API Changes & Projects strucutre changes +API Changes & Project structure changes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - ``geotrellis.proj4j`` diff --git a/proj4/src/main/scala/geotrellis/proj4/CRS.scala b/proj4/src/main/scala/geotrellis/proj4/CRS.scala index c7dc74e26d..ee14e4bb71 100644 --- a/proj4/src/main/scala/geotrellis/proj4/CRS.scala +++ b/proj4/src/main/scala/geotrellis/proj4/CRS.scala @@ -19,21 +19,12 @@ package geotrellis.proj4 import geotrellis.proj4.io.wkt.WKT import org.locationtech.proj4j._ -import com.github.blemale.scaffeine.Scaffeine +import org.locationtech.proj4j.util.CRSCache -import scala.io.Source import scala.util.Try - object CRS { - private lazy val proj4ToEpsgMap = - Scaffeine() - .recordStats() - .build[String, Option[String]]() - - // new Memoize[String, Option[String]](readEpsgCodeFromFile) - private val crsFactory = new CRSFactory - private val filePrefix = "/proj4/nad/" + private val crsFactory = new CRSCache() /** * Creates a CoordinateReferenceSystem @@ -48,15 +39,12 @@ object CRS { * @return The specified CoordinateReferenceSystem */ def fromString(proj4Params: String): CRS = - new CRS { - val proj4jCrs: CoordinateReferenceSystem = crsFactory.createFromParameters(null, proj4Params) - } + new CRS { val proj4jCrs: CoordinateReferenceSystem = crsFactory.createFromParameters(null, proj4Params) } /** * Returns the numeric EPSG code of a proj4string. */ - def getEpsgCode(proj4String: String): Option[Int] = - proj4ToEpsgMap.get(proj4String, { key => readEpsgCodeFromFile(key) }).map(_.toInt) + def getEpsgCode(proj4String: String): Option[Int] = readEpsgFromParameters(proj4String).map(_.toInt) /** * Creates a CoordinateReferenceSystem @@ -72,9 +60,7 @@ object CRS { * @return The specified CoordinateReferenceSystem */ def fromString(name: String, proj4Params: String): CRS = - new CRS { - val proj4jCrs: CoordinateReferenceSystem = crsFactory.createFromParameters(name, proj4Params) - } + new CRS { val proj4jCrs: CoordinateReferenceSystem = crsFactory.createFromParameters(name, proj4Params) } /** * Creates a CoordinateReferenceSystem (CRS) from a @@ -82,7 +68,6 @@ object CRS { */ def fromWKT(wktString: String): CRS = { val epsgCode: String = WKT.getEpsgCode(wktString) - fromName(epsgCode) } @@ -112,39 +97,15 @@ object CRS { * @return The CoordinateReferenceSystem corresponding to the given name */ def fromName(name: String): CRS = - new CRS { - val proj4jCrs: CoordinateReferenceSystem = crsFactory.createFromName(name) - } + new CRS { val proj4jCrs: CoordinateReferenceSystem = crsFactory.createFromName(name) } /** * Creates a CoordinateReferenceSystem (CRS) from an EPSG code. */ - def fromEpsgCode(epsgCode: Int) = - fromName(s"EPSG:$epsgCode") - - private def readEpsgCodeFromFile(proj4String: String): Option[String] = { - // TODO: move this functinality to org.locationtech.proj4j.Proj4FileReaeder - val stream = crsFactory.getClass.getResourceAsStream(s"${filePrefix}epsg") - - try { - Source.fromInputStream(stream) - .getLines - .find { line => - !line.startsWith("#") && { - // FIX this match is sensative to white spaces and ordering - val proj4Body = line.split("proj")(1) - s"+proj$proj4Body" == proj4String - } - }.flatMap { l => - val array = l.split(" ") - val length = array(0).length - // read the int ... - Some(array(0).substring(1, length - 1)) - } - } finally { - stream.close() - } - } + def fromEpsgCode(epsgCode: Int) = fromName(s"EPSG:$epsgCode") + + def readEpsgFromParameters(proj4String: String): Option[String] = + Option(crsFactory.readEpsgFromParameters(proj4String)) /** Mix-in for singleton CRS implementations where distinguished string should be the name of the object. */ private[proj4] trait ObjectNameToString { self: CRS ⇒ @@ -159,10 +120,8 @@ trait CRS extends Serializable { def epsgCode: Option[Int] = { proj4jCrs.getName.split(":") match { - case Array(name, code) if name.toUpperCase == "EPSG" => - Try(code.toInt).toOption - case _ => - CRS.getEpsgCode(toProj4String + " <>") + case Array(name, code) if name.toUpperCase == "EPSG" => Try(code.toInt).toOption + case _ => CRS.getEpsgCode(toProj4String) } } @@ -184,55 +143,18 @@ trait CRS extends Serializable { // TODO: Do these better once more things are ported - override - def hashCode = toProj4String.hashCode + override def hashCode = toProj4String.hashCode def toProj4String: String = proj4jCrs.getParameterString def isGeographic: Boolean = proj4jCrs.isGeographic - override - def equals(o: Any): Boolean = + override def equals(o: Any): Boolean = o match { - case other: CRS => compareProj4Strings(other.toProj4String, toProj4String) + case other: CRS => proj4jCrs == other.proj4jCrs case _ => false } - private def compareProj4Strings(p1: String, p2: String) = { - def toProj4Map(s: String): Map[String, String] = - s.trim.split(" ").map(x => - if (x.startsWith("+")) x.substring(1) else x).map(x => { - val index = x.indexOf('=') - if (index != -1) (x.substring(0, index) -> Some(x.substring(index + 1))) - else (x -> None) - }).groupBy(_._1).map { case (a, b) => (a, b.head._2) } - .filter { case (a, b) => !b.isEmpty }.map { case (a, b) => (a -> b.get) } - .map { case (a, b) => if (b == "latlong") (a -> "longlat") else (a, b) } - .filter { case (a, b) => (a != "to_meter" || b != "1.0") } - - val m1 = toProj4Map(p1) - val m2 = toProj4Map(p2) - - m1.map { - case (key, v1) => m2.get(key) match { - case Some(v2) => compareValues(v1, v2) - case None => false - } - }.forall(_ != false) - } - - private def compareValues(s1: String, s2: String) = { - def isNumber(s: String) = s.filter(c => !List('.', '-').contains(c)) forall Character.isDigit - - val s2IsNumber = isNumber(s1) - val s1IsNumber = isNumber(s2) - - if (s1IsNumber == s2IsNumber) { - if (s1IsNumber) math.abs(s1.toDouble - s2.toDouble) < Epsilon - else s1 == s2 - } else false - } - protected def factory = CRS.crsFactory /** Default implementation returns the proj4 name. */ diff --git a/proj4/src/main/scala/geotrellis/proj4/ConusAlbers.scala b/proj4/src/main/scala/geotrellis/proj4/ConusAlbers.scala index 943b8f4cda..780ccb862e 100644 --- a/proj4/src/main/scala/geotrellis/proj4/ConusAlbers.scala +++ b/proj4/src/main/scala/geotrellis/proj4/ConusAlbers.scala @@ -21,5 +21,5 @@ import geotrellis.proj4.CRS.ObjectNameToString object ConusAlbers extends CRS with ObjectNameToString { lazy val proj4jCrs = factory.createFromName("EPSG:5070") - override def epsgCode: Option[Int] = Some(5070) + override val epsgCode: Option[Int] = Some(5070) } diff --git a/proj4/src/main/scala/geotrellis/proj4/LatLng.scala b/proj4/src/main/scala/geotrellis/proj4/LatLng.scala index d63945040f..f956ec9ef3 100644 --- a/proj4/src/main/scala/geotrellis/proj4/LatLng.scala +++ b/proj4/src/main/scala/geotrellis/proj4/LatLng.scala @@ -21,5 +21,5 @@ import geotrellis.proj4.CRS.ObjectNameToString object LatLng extends CRS with ObjectNameToString { lazy val proj4jCrs = factory.createFromName("EPSG:4326") - override def epsgCode: Option[Int] = Some(4326) + override val epsgCode: Option[Int] = Some(4326) } diff --git a/proj4/src/main/scala/geotrellis/proj4/WebMercator.scala b/proj4/src/main/scala/geotrellis/proj4/WebMercator.scala index e8302194a3..25ceb9b8a6 100644 --- a/proj4/src/main/scala/geotrellis/proj4/WebMercator.scala +++ b/proj4/src/main/scala/geotrellis/proj4/WebMercator.scala @@ -21,5 +21,5 @@ import geotrellis.proj4.CRS.ObjectNameToString object WebMercator extends CRS with ObjectNameToString { lazy val proj4jCrs = factory.createFromName("EPSG:3857") - override def epsgCode: Option[Int] = Some(3857) + override val epsgCode: Option[Int] = Some(3857) } diff --git a/project/Dependencies.scala b/project/Dependencies.scala index 5e6e7e88c7..1c96d3638b 100644 --- a/project/Dependencies.scala +++ b/project/Dependencies.scala @@ -22,7 +22,7 @@ object Dependencies { val scalatest = "org.scalatest" %% "scalatest" % "3.0.5" val scalacheck = "org.scalacheck" %% "scalacheck" % "1.14.0" val jts = "org.locationtech.jts" % "jts-core" % "1.16.0" - val proj4j = "org.locationtech.proj4j" % "proj4j" % "1.0.0" + val proj4j = "org.locationtech.proj4j" % "proj4j" % "1.1.0-SNAPSHOT" val monocleCore = "com.github.julien-truffaut" %% "monocle-core" % Version.monocle val monocleMacro = "com.github.julien-truffaut" %% "monocle-macro" % Version.monocle diff --git a/project/Settings.scala b/project/Settings.scala index e82ddb8fed..54ea4485f4 100644 --- a/project/Settings.scala +++ b/project/Settings.scala @@ -33,7 +33,10 @@ object Settings { val osgeo = "osgeo" at "http://download.osgeo.org/webdav/geotools/" val geowaveRelease = "geowave-release" at "http://geowave-maven.s3-website-us-east-1.amazonaws.com/release" val geowaveSnapshot = "geowave-snapshot" at "http://geowave-maven.s3-website-us-east-1.amazonaws.com/snapshot" - val local = Resolver.file("local", file(Path.userHome.absolutePath + "/.ivy2/local"))(Resolver.ivyStylePatterns) + val ivy2Local = Resolver.file("local", file(Path.userHome.absolutePath + "/.ivy2/local"))(Resolver.ivyStylePatterns) + val mavenLocal = Resolver.mavenLocal + + val local = Seq(ivy2Local, mavenLocal) } lazy val noForkInTests = Seq( @@ -149,12 +152,8 @@ object Settings { // This is one finicky dependency. Being explicit in hopes it will stop hurting Travis. jaiCore % Test from "http://download.osgeo.org/webdav/geotools/javax/media/jai_core/1.1.3/jai_core-1.1.3.jar" ), - externalResolvers := Seq( - Repositories.geosolutions, - Repositories.osgeo, - Repositories.boundlessgeo, - DefaultMavenRepository, - Repositories.local + externalResolvers ++= Seq( + Repositories.boundlessgeo ), initialCommands in console := """ @@ -214,7 +213,6 @@ object Settings { scalatest % Test ), resolvers ++= Seq( - Resolver.mavenLocal, Repositories.boundlessgeoRelease, Repositories.geosolutions, Repositories.geowaveRelease, @@ -304,7 +302,6 @@ object Settings { lazy val proj4 = Seq( name := "geotrellis-proj4", - resolvers ++= Seq(Resolver.mavenLocal), libraryDependencies ++= Seq( proj4j, openCSV, diff --git a/project/Version.scala b/project/Version.scala index 8d61b41b05..a77a4b2954 100644 --- a/project/Version.scala +++ b/project/Version.scala @@ -15,7 +15,7 @@ */ object Version { - val geotrellis = "2.3.2-SNAPSHOT" + val geotrellis = "2.3.2" + Environment.versionSuffix val scala = "2.11.12" val crossScala = Seq(scala, "2.12.7") val geotools = "20.0" diff --git a/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/GeoTiffReaderSpec.scala b/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/GeoTiffReaderSpec.scala index 9308956a6c..1b62a35869 100644 --- a/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/GeoTiffReaderSpec.scala +++ b/raster/src/test/scala/geotrellis/raster/io/geotiff/reader/GeoTiffReaderSpec.scala @@ -279,7 +279,8 @@ class GeoTiffReaderSpec extends FunSpec it("should read econic.tif CS correctly") { val crs = SinglebandGeoTiff(s"$baseDataPath/econic.tif", streaming = true).crs - val correctProj4String = "+proj=eqdc +lat_0=33.76446202777777 +lon_0=-117.4745428888889 +lat_1=33.90363402777778 +lat_2=33.62529002777778 +x_0=0 +y_0=0 +datum=NAD27 +units=m +no_defs" + // pulled from file directly, gdalinfo lies and will round each paramete value to 17 characters for presentation + val correctProj4String = "+proj=eqdc +lat_1=33.90363402777778 +lat_2=33.62529002777778 +lat_0=33.764462027777775 +lon_0=-117.47454288888889 +x_0=0.0 +y_0=0.0 +datum=NAD27 +units=m" val correctCRS = CRS.fromString(correctProj4String)