From 0275d9e79da1f72044737fb0fbcaa4f136f09c6f Mon Sep 17 00:00:00 2001 From: Andrew Harvey Date: Mon, 5 Jul 2021 12:01:04 +1000 Subject: update within range to accept comparing ranges --- lib/withinRange.js | 60 +++++++++++++++++++++++++++------ test/withinRange.js | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 144 insertions(+), 13 deletions(-) diff --git a/lib/withinRange.js b/lib/withinRange.js index 7b6aa18..b86da82 100644 --- a/lib/withinRange.js +++ b/lib/withinRange.js @@ -1,38 +1,76 @@ /** * @param {Object} feature * @param {Object} rangeFeature - * @param {Object} options - * @param {boolean} options.matchParity - if the parity of the number must match the range to be considered within (eg. if true, then 2 would not be within 1-3 but would be within 2-4 or within 0-4) + * @param {Object} [options] + * @param {boolean} [options.matchParity=false] - if the parity of the number must match the range to be considered within (eg. if true, then 2 would not be within 1-3 but would be within 2-4 or within 0-4) + * @param {boolean} [options.checkHigherOrderAddrKeys=true] - if true checks that addr:suburb, addr:state, addr:postcode also match + * @param {boolean} [options.checkStreet=true] - if true checks that addr:street matches * * @returns {boolean} True if addr:housenumber of feature is within the range of addr:housenumber rangeFeature and all other addr:* attributes match */ module.exports = (feature, rangeFeature, options) => { const regexp = /^(?
\D*)(?\d*)(?\D*)$/
 
+  const checkStreet = options && 'checkStreet' in options ? options.checkStreet : true
+  const checkHigherOrderAddrKeys = options && 'checkHigherOrderAddrKeys' in options ? options.checkHigherOrderAddrKeys : true
+
   if (
     // must have a housenumber
     'addr:housenumber' in feature.properties &&
     'addr:housenumber' in rangeFeature.properties &&
 
     // must have a street and street must match
-    'addr:street' in feature.properties &&
-    'addr:street' in rangeFeature.properties &&
-    feature.properties['addr:street'] === rangeFeature.properties['addr:street'] &&
+    (
+      checkStreet ? (
+        'addr:street' in feature.properties &&
+        'addr:street' in rangeFeature.properties &&
+        (feature.properties['addr:street'] || '').toLowerCase().replaceAll(' ', '') === (rangeFeature.properties['addr:street'] || '').toLowerCase().replaceAll(' ', '')
+      ) : true
+    ) &&
 
     // other higher attributes must match if exists
-    feature.properties['addr:suburb'] === rangeFeature.properties['addr:suburb'] &&
-    feature.properties['addr:state'] === rangeFeature.properties['addr:state'] &&
-    feature.properties['addr:postcode'] === rangeFeature.properties['addr:postcode']
+    (
+      checkHigherOrderAddrKeys ? (
+        feature.properties['addr:suburb'] === rangeFeature.properties['addr:suburb'] &&
+        feature.properties['addr:state'] === rangeFeature.properties['addr:state'] &&
+        feature.properties['addr:postcode'] === rangeFeature.properties['addr:postcode']
+      ) : true
+    )
   ) {
     const rangeParts = rangeFeature.properties['addr:housenumber'].split('-')
     if (rangeParts.length === 2) {
+      const fromMatch = rangeParts[0].match(regexp)
+      const toMatch = rangeParts[1].match(regexp)
+
+      if (!fromMatch || !toMatch) {
+        console.log(`range ${rangeFeature.properties['addr:housenumber']} didn't match regexp`, rangeFeature)
+        return false
+      }
       const from = rangeParts[0].match(regexp).groups
       const to = rangeParts[1].match(regexp).groups
 
-      const i = feature.properties['addr:housenumber'].match(regexp).groups
+      const iParts = feature.properties['addr:housenumber'].split('-')
+      let iFrom
+      let iTo
+      let iRange = false
+      if (iParts.length === 2) {
+        iRange = true
+        iFrom = iParts[0].match(regexp).groups
+        iTo = iParts[1].match(regexp).groups
+      }
+      const i = !iRange ? feature.properties['addr:housenumber'].match(regexp).groups : null
       if (
-        Number.isInteger(Number(i.num)) && Number.isInteger(Number(from.num)) && Number.isInteger(Number(to.num)) &&
-        Number(i.num) >= Number(from.num) && Number(i.num) <= Number(to.num)
+        iRange ? (
+          Number.isInteger(Number(iFrom.num)) && Number.isInteger(Number(iTo.num)) && Number.isInteger(Number(from.num)) && Number.isInteger(Number(to.num)) &&
+          (
+            (Number(iFrom.num) >= Number(from.num) && Number(iFrom.num) <= Number(to.num))
+            ||
+            (Number(iTo.num) >= Number(from.num) && Number(iTo.num) <= Number(to.num))
+          )
+        ) : (
+          Number.isInteger(Number(i.num)) && Number.isInteger(Number(from.num)) && Number.isInteger(Number(to.num)) &&
+          Number(i.num) >= Number(from.num) && Number(i.num) <= Number(to.num)
+        )
       ) {
         // feature within featureRange (ignore prefix/suffix)
         if (options && options.matchParity) {
diff --git a/test/withinRange.js b/test/withinRange.js
index 8d46eba..c2d37de 100644
--- a/test/withinRange.js
+++ b/test/withinRange.js
@@ -93,6 +93,75 @@ const rangeOutsideSub = {
   }
 }
 
+const B_withSuburb = {
+  "type": "Feature",
+  "properties": {
+    "addr:housenumber": "2",
+    "addr:street": "Main Street",
+    "addr:suburb": "Suburb A"
+  },
+  "geometry": {
+    "type": "Point",
+    "coordinates": [0, 0]
+  }
+}
+const AC_withDifferentSuburb = {
+  "type": "Feature",
+  "properties": {
+    "addr:housenumber": "1-3",
+    "addr:street": "Main Street",
+    "addr:suburb": "Suburb B"
+  },
+  "geometry": {
+    "type": "Point",
+    "coordinates": [0, 0]
+  }
+}
+
+const AD = {
+  "type": "Feature",
+  "properties": {
+    "addr:housenumber": "1-4",
+    "addr:street": "Main Street"
+  },
+  "geometry": {
+    "type": "Point",
+    "coordinates": [0, 0]
+  }
+}
+const BC = {
+  "type": "Feature",
+  "properties": {
+    "addr:housenumber": "2-3",
+    "addr:street": "Main Street"
+  },
+  "geometry": {
+    "type": "Point",
+    "coordinates": [0, 0]
+  }
+}
+const CE = {
+  "type": "Feature",
+  "properties": {
+    "addr:housenumber": "3-5",
+    "addr:street": "Main Street"
+  },
+  "geometry": {
+    "type": "Point",
+    "coordinates": [0, 0]
+  }
+}
+const DE = {
+  "type": "Feature",
+  "properties": {
+    "addr:housenumber": "4-5",
+    "addr:street": "Main Street"
+  },
+  "geometry": {
+    "type": "Point",
+    "coordinates": [0, 0]
+  }
+}
 
 test('withinRange', t => {
   t.same(
@@ -138,9 +207,33 @@ test('withinRange', t => {
   )
 
   t.same(
-    withinRange(A, AC, { matchParity: true }),
+    withinRange(B_withSuburb, AC_withDifferentSuburb),
+    false,
+    'by default checks suburbs match for within to pass'
+  )
+
+  t.same(
+    withinRange(B_withSuburb, AC_withDifferentSuburb, {
+      checkHigherOrderAddrKeys: false
+    }),
+    true,
+    'within range when ignoring higher order addr keys'
+  )
+
+  t.same(
+    withinRange(BC, AD),
+    true,
+    'range completely within range'
+  )
+  t.same(
+    withinRange(CE, AD),
+    true,
+    'range overlapping within range'
+  )
+  t.same(
+    withinRange(DE, AD),
     true,
-    'A within AC when matching parity'
+    'range touching endpoints of range'
   )
 
   t.end()
-- 
cgit v1.2.3