diff --git a/geocoder/src/main/scala/grasshopper/geocoder/search/census/AddressInterpolator.scala b/geocoder/src/main/scala/grasshopper/geocoder/search/census/AddressInterpolator.scala index 1fe76b3..38403b0 100644 --- a/geocoder/src/main/scala/grasshopper/geocoder/search/census/AddressInterpolator.scala +++ b/geocoder/src/main/scala/grasshopper/geocoder/search/census/AddressInterpolator.scala @@ -17,14 +17,9 @@ object AddressInterpolator { val s = feature.values.getOrElse(s"${pre}FROMHN", "0") val e = feature.values.getOrElse(s"${pre}TOHN", "0") - val start = s match { - case "" => 0 - case _ => toInt(s.toString).getOrElse(0) - } - val end = e match { - case "" => 0 - case _ => toInt(e.toString).getOrElse(0) - } + val start = toInt(s.toString).getOrElse(0) + + val end = toInt(e.toString).getOrElse(0) AddressRange(start, end) } @@ -98,18 +93,12 @@ object AddressInterpolator { private def rangeIsEven(f: Feature, isLeft: Boolean): Boolean = { val prefix = if (isLeft) "L" else "R" val fromEven = f.values.getOrElse(s"${prefix}FROMHN", 0) - val fromRange = fromEven match { - case "" => 0 - case _ => toInt(fromEven.toString).getOrElse(0) - } - val fromIsEven = fromEven != "" && fromRange % 2 == 0 + val fromRange = toInt(fromEven.toString).getOrElse(0) + val fromIsEven = fromRange != None && fromRange % 2 == 0 val toEven = f.values.getOrElse(s"${prefix}TOHN", 0) - val toRange = fromEven match { - case "" => 0 - case _ => toInt(toEven.toString).getOrElse(0) - } - val toIsEven = toEven != "" && toRange % 2 == 0 + val toRange = toInt(toEven.toString).getOrElse(0) + val toIsEven = toRange != None && toRange % 2 == 0 fromIsEven || toIsEven } diff --git a/geocoder/src/main/scala/grasshopper/geocoder/search/census/SearchUtils.scala b/geocoder/src/main/scala/grasshopper/geocoder/search/census/SearchUtils.scala index de02da8..8936f66 100644 --- a/geocoder/src/main/scala/grasshopper/geocoder/search/census/SearchUtils.scala +++ b/geocoder/src/main/scala/grasshopper/geocoder/search/census/SearchUtils.scala @@ -12,10 +12,14 @@ object SearchUtils { if (isNumeric(s)) { Some(s.toInt) } else { - if (s.contains("-")) { - Some(s.substring(0, s.indexOf("-")).toInt) + if (s.contains(".")) { + Some(s.substring(0, s.indexOf(".")).toInt) + } else if (s.contains("-")) { + Some(s.substring(0, s.indexOf("-", 1)).toInt) } else if (s == "None") { None + } else if (s == "") { + None } else { Some(s.replaceAll("[^\\d]", "").toInt) } @@ -28,6 +32,6 @@ object SearchUtils { } def isNumeric(str: String): Boolean = { - str.matches("-?\\d+(\\.\\d+)?") + str.matches("-?\\d+") } } diff --git a/geocoder/src/test/scala/grasshopper/geocoder/search/census/AddressInterpolatorSpec.scala b/geocoder/src/test/scala/grasshopper/geocoder/search/census/AddressInterpolatorSpec.scala new file mode 100644 index 0000000..df4e38d --- /dev/null +++ b/geocoder/src/test/scala/grasshopper/geocoder/search/census/AddressInterpolatorSpec.scala @@ -0,0 +1,84 @@ +package grasshopper.geocoder.search.census + +import org.scalatest.{ PropSpec, MustMatchers, PrivateMethodTester } +import org.scalatest.prop.PropertyChecks +import grasshopper.geocoder.search.census.AddressInterpolator._ +import grasshopper.model.census.AddressRange +import feature._ + +class AddressInterpolatorSpec extends PropSpec with MustMatchers with PropertyChecks with PrivateMethodTester with NumericGenerators { + + property("calculateDistance must always be positive") { + forAll(positive, positive) { (start, stop) => + val range = new AddressRange(start, stop) + val calculateDistance = PrivateMethod[Double]('calculateDistance) + AddressInterpolator invokePrivate calculateDistance(range) mustBe >=(0.0) + } + } + + val testField = new Field("test", StringType()) + val testSchema = new Schema(Iterable(testField, testField)) + + property("for rangeIsEven left even is even") { + forAll(even, even) { (x, y) => + val range = Map("LFROMHN" -> x, "LTOHN" -> y) + val evenFeature = new Feature(1, testSchema, range) + val rangeIsEven = PrivateMethod[Boolean]('rangeIsEven) + AddressInterpolator invokePrivate rangeIsEven(evenFeature, true) mustBe true + } + } + + property("for rangeIsEven left odd is odd") { + forAll(odd, odd) { (x, y) => + whenever(x != 0 && y != 0) { + val range = Map("LFROMHN" -> x, "LTOHN" -> y) + val oddFeature = new Feature(1, testSchema, range) + val rangeIsEven = PrivateMethod[Boolean]('rangeIsEven) + AddressInterpolator invokePrivate rangeIsEven(oddFeature, true) mustBe false + } + } + } + + property("for rangeIsEven right even is even") { + forAll(even, even) { (x, y) => + val range = Map("RFROMHN" -> x, "RTOHN" -> y) + val evenFeature = new Feature(1, testSchema, range) + val rangeIsEven = PrivateMethod[Boolean]('rangeIsEven) + AddressInterpolator invokePrivate rangeIsEven(evenFeature, false) mustBe true + } + } + + property("for rangeIsEven right odd is odd") { + forAll(odd, odd) { (x, y) => + whenever(x != 0 && y != 0) { + val range = Map("RFROMHN" -> x, "RTOHN" -> y) + val oddFeature = new Feature(1, testSchema, range) + val rangeIsEven = PrivateMethod[Boolean]('rangeIsEven) + AddressInterpolator invokePrivate rangeIsEven(oddFeature, false) mustBe false + } + } + } + + property("for calculateAddressRange left even, right odd") { + forAll(odd, odd, even, even, digits) { (r1, r2, l1, l2, a) => + whenever(r1 != 0 && r2 != 0) { + val range = Map("RFROMHN" -> r1, "RTOHN" -> r2, "LFROMHN" -> l1, "LTOHN" -> l2) + val feature = new Feature(1, testSchema, range) + if (a % 2 == 0) calculateAddressRange(feature, a) mustBe (AddressRange(l1, l2)) + else calculateAddressRange(feature, a) mustBe (AddressRange(r1, r2)) + } + } + } + + property("for calculateAddressRange left odd, right even") { + forAll(even, even, odd, odd, digits) { (r1, r2, l1, l2, a) => + whenever(l1 != 0 && l2 != 0) { + val range = Map("RFROMHN" -> r1, "RTOHN" -> r2, "LFROMHN" -> l1, "LTOHN" -> l2) + val feature = new Feature(1, testSchema, range) + if (a % 2 == 0) calculateAddressRange(feature, a) mustBe (AddressRange(r1, r2)) + else calculateAddressRange(feature, a) mustBe (AddressRange(l1, l2)) + } + } + } + +} \ No newline at end of file diff --git a/geocoder/src/test/scala/grasshopper/geocoder/search/census/NumericGenerators.scala b/geocoder/src/test/scala/grasshopper/geocoder/search/census/NumericGenerators.scala new file mode 100644 index 0000000..3009e04 --- /dev/null +++ b/geocoder/src/test/scala/grasshopper/geocoder/search/census/NumericGenerators.scala @@ -0,0 +1,31 @@ +package grasshopper.geocoder.search.census + +import org.scalacheck.Gen + +trait NumericGenerators { + + def digits: Gen[Int] = { + for { + n <- Gen.choose(-10000, 10000) + } yield n + } + + def positive: Gen[Int] = { + for { + n <- Gen.choose(0, 10000) + } yield n + } + + def even: Gen[Int] = { + for { + n <- Gen.choose(-10000, 10000) + } yield n * 2 + } + + def odd: Gen[Int] = { + for { + n <- Gen.choose(-10000, 10000) + } yield n * 2 - 1 + } + +} \ No newline at end of file diff --git a/geocoder/src/test/scala/grasshopper/geocoder/search/census/SearchUtilsSpec.scala b/geocoder/src/test/scala/grasshopper/geocoder/search/census/SearchUtilsSpec.scala new file mode 100644 index 0000000..d12ba13 --- /dev/null +++ b/geocoder/src/test/scala/grasshopper/geocoder/search/census/SearchUtilsSpec.scala @@ -0,0 +1,28 @@ +package grasshopper.geocoder.search.census + +import org.scalatest.{ PropSpec, MustMatchers } +import org.scalatest.prop.PropertyChecks +import grasshopper.geocoder.search.census.SearchUtils._ + +class SearchUtilsSpec extends PropSpec with MustMatchers with PropertyChecks with NumericGenerators { + + property("digits are parsed into Ints") { + forAll(digits) { n => + toInt(n.toString) mustBe Some(n) + } + } + + property("decimals are parsed into Ints") { + forAll(digits, positive) { (x, y) => + val numString = x.toString + "." + y.toString + toInt(numString) mustBe Some(x) + } + } + + property("hyphens are parsed into Ints") { + forAll(digits, positive) { (x, y) => + val numString = x.toString + "-" + y.toString + toInt(numString) mustBe Some(x) + } + } +} diff --git a/geocoder/src/test/scala/grasshopper/geocoder/search/census/interpolateSpec.scala b/geocoder/src/test/scala/grasshopper/geocoder/search/census/interpolateSpec.scala new file mode 100644 index 0000000..402c575 --- /dev/null +++ b/geocoder/src/test/scala/grasshopper/geocoder/search/census/interpolateSpec.scala @@ -0,0 +1,38 @@ +package grasshopper.geocoder.search.census + +import org.scalatest.{ MustMatchers, FlatSpec } +import grasshopper.geocoder.search.census.AddressInterpolator._ +import grasshopper.model.census.AddressRange +import feature._ +import geometry._ + +class InterpolateSpec extends FlatSpec with MustMatchers { + + "A correct point" must "be found on the right side of a horizontal line" in { + val values: Map[String, Any] = Map( + "geometry" -> read("LINESTRING (0 0, 100 0)"), + "FULLNAME" -> "999 Main Street", + "RFROMHN" -> 1, + "RTOHN" -> 101, + "LFROMHN" -> 0, + "LTOHN" -> 100, + "ZIPR" -> "19041", + "ZIPL" -> "19041", + "STATE" -> "CA" + ) + + val addressField = Field("address", StringType()) + val geomField = Field("geometry", GeometryType()) + val numberField = Field("number", IntType()) + val schema = Schema(geomField, addressField, numberField) + val feature = Feature(schema, values) + val addressNumber = 25 + val addressRange = AddressRange(0, 100) + + val newFeature = interpolate(feature, addressRange, addressNumber) + + newFeature.geometry mustBe ((25, .0001)) + + } + +} \ No newline at end of file diff --git a/project/Version.scala b/project/Version.scala index a6ff555..c384942 100644 --- a/project/Version.scala +++ b/project/Version.scala @@ -4,7 +4,7 @@ object Version { val config = "1.3.0" val akka = "2.4.4" val scalaTest = "3.0.0-M15" - val scalaCheck = "1.13.0" + val scalaCheck = "1.12.5" val elasticsearch = "2.2.0" val jts = "1.13" val scale = "0.0.2"