imerss-bioinfo 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/README.md +156 -0
  2. package/build.js +196 -0
  3. package/docs/css/imerss-viz-all.css +1227 -0
  4. package/docs/css/imerss-viz-wp-overrides.css +70 -0
  5. package/docs/css/pepiowelh.css +76 -0
  6. package/docs/css/xetthecum-external.css +47 -0
  7. package/docs/css/xetthecum-shared.css +80 -0
  8. package/docs/css/xetthecum.css +526 -0
  9. package/docs/js/imerss-viz-all.js +58 -0
  10. package/docs/js/imerss-viz-all.js.map +1 -0
  11. package/docs/js/jquery.js +10881 -0
  12. package/index.html +81 -0
  13. package/index.js +7 -0
  14. package/indexWithJustMap.html +92 -0
  15. package/indexWithMap.html +79 -0
  16. package/indexWithMapHoweSound.html +91 -0
  17. package/indexWithMapPepiowelh.html +119 -0
  18. package/indexWithMapValdes.html +90 -0
  19. package/indexWithMapXetthecum.html +90 -0
  20. package/package.json +50 -0
  21. package/src/WoRMS/taxonAPI.js +13 -0
  22. package/src/arphify.js +662 -0
  23. package/src/assignNames.js +103 -0
  24. package/src/auxBuild/restoreJQuery.js +1 -0
  25. package/src/buildSource/Comprehensive Lists.html +42 -0
  26. package/src/buildSource/Data Paper Part I Visualisation.html +50 -0
  27. package/src/buildSource/Galiano Life List.html +43 -0
  28. package/src/buildSource/Metchosin/index.html +22 -0
  29. package/src/buildSource/Pe'pi'ow'elh.html +72 -0
  30. package/src/buildSource/Squamish Life List.html +48 -0
  31. package/src/buildSource/Valdes Island Biodiversity.html +64 -0
  32. package/src/buildSource/Xetthecum.html +40 -0
  33. package/src/buildSource/dataPaperSunburstAndMap.html +30 -0
  34. package/src/buildSource/index.html +224 -0
  35. package/src/client/css/imerss-viz-wp-overrides.css +70 -0
  36. package/src/client/css/imerss-viz.css +531 -0
  37. package/src/client/css/pepiowelh.css +76 -0
  38. package/src/client/css/xetthecum-external.css +47 -0
  39. package/src/client/css/xetthecum-shared.css +80 -0
  40. package/src/client/css/xetthecum.css +526 -0
  41. package/src/client/html/imerss-viz-map-only.html +75 -0
  42. package/src/client/html/imerss-viz-map-pepi.html +77 -0
  43. package/src/client/html/imerss-viz-map-xetthecum.html +90 -0
  44. package/src/client/html/imerss-viz-map.html +75 -0
  45. package/src/client/html/imerss-viz.html +58 -0
  46. package/src/client/html/importing Xetthecum patterns.txt +10 -0
  47. package/src/client/html/xetthecum-patterns.html +27 -0
  48. package/src/client/img/Animalia_2.svg +1 -0
  49. package/src/client/img/Animalia_Annelida.svg +655 -0
  50. package/src/client/img/Animalia_Chaetognatha.svg +72 -0
  51. package/src/client/img/Animalia_Cnidaria.svg +173 -0
  52. package/src/client/img/Animalia_Ctenophora.svg +298 -0
  53. package/src/client/img/Animalia_Nemertea.svg +54 -0
  54. package/src/client/img/Animalia_Phoronida.svg +271 -0
  55. package/src/client/img/Animalia_Platyhelminthes.svg +58 -0
  56. package/src/client/img/Animalia_Porifera.svg +220 -0
  57. package/src/client/img/Animalia_Sipuncula.svg +155 -0
  58. package/src/client/img/Arachnoidiscus_schmidtii_web_1-2-scaled.webp +0 -0
  59. package/src/client/img/Arrow-down.svg +11 -0
  60. package/src/client/img/Arrow-up.svg +6 -0
  61. package/src/client/img/Chromista.svg +1 -0
  62. package/src/client/img/Pepiowelh/BlackNoWe2-wb-bb.png +0 -0
  63. package/src/client/img/Pepiowelh/BlackNoWe2-wb.png +0 -0
  64. package/src/client/img/Pepiowelh/BlackNoWe2.png +0 -0
  65. package/src/client/img/Pepiowelh/Ecocultural Mapping Banner.svg +1 -0
  66. package/src/client/img/Plankton.jpg +0 -0
  67. package/src/client/img/Plantae.svg +1 -0
  68. package/src/client/img/Plantae_2.svg +1 -0
  69. package/src/client/img/Protista.svg +1 -0
  70. package/src/client/img/Valdes/Valdes Island Conservancy.jpg +0 -0
  71. package/src/client/img/Xetthecum/Agricultural.jpg +0 -0
  72. package/src/client/img/Xetthecum/Anthropogenic_banner.jpg +0 -0
  73. package/src/client/img/Xetthecum/Broad_Leaf.jpg +0 -0
  74. package/src/client/img/Xetthecum/Caves.jpg +0 -0
  75. package/src/client/img/Xetthecum/Coastal_Bluffs.jpg +0 -0
  76. package/src/client/img/Xetthecum/Conifer_Pole_Sapling.jpg +0 -0
  77. package/src/client/img/Xetthecum/Developed.jpg +0 -0
  78. package/src/client/img/Xetthecum/Dock.jpg +0 -0
  79. package/src/client/img/Xetthecum/Eelgrass.jpg +0 -0
  80. package/src/client/img/Xetthecum/Forests_banner.jpg +0 -0
  81. package/src/client/img/Xetthecum/Freshwater_banner.jpg +0 -0
  82. package/src/client/img/Xetthecum/Greig_Creek.jpg +0 -0
  83. package/src/client/img/Xetthecum/Intertidal.jpg +0 -0
  84. package/src/client/img/Xetthecum/Laughlin_Lake.jpg +0 -0
  85. package/src/client/img/Xetthecum/Marine_banner.jpg +0 -0
  86. package/src/client/img/Xetthecum/Mature_Conifer_Forest.jpg +0 -0
  87. package/src/client/img/Xetthecum/Porlier_Pass_Road.jpg +0 -0
  88. package/src/client/img/Xetthecum/Riparian.jpg +0 -0
  89. package/src/client/img/Xetthecum/Rural.jpg +0 -0
  90. package/src/client/img/Xetthecum/Subtidal.jpg +0 -0
  91. package/src/client/img/Xetthecum/Wetland.jpg +0 -0
  92. package/src/client/img/Xetthecum/Woodland.jpg +0 -0
  93. package/src/client/img/Xetthecum/Woodland_banner.jpg +0 -0
  94. package/src/client/img/Xetthecum/Xetthecum.jpg +0 -0
  95. package/src/client/img/Xetthecum/Young_Conifer_Forest.jpg +0 -0
  96. package/src/client/img/Xetthecum/Young_Mixed_Forest.jpg +0 -0
  97. package/src/client/img/Xetthecum/food.svg +38 -0
  98. package/src/client/img/Xetthecum/indicator.svg +52 -0
  99. package/src/client/img/Xetthecum/material.svg +186 -0
  100. package/src/client/img/Xetthecum/medicinal.svg +62 -0
  101. package/src/client/img/Xetthecum/missing-value.svg +3 -0
  102. package/src/client/img/Xetthecum/spiritual.svg +13 -0
  103. package/src/client/img/Xetthecum/trade.svg +74 -0
  104. package/src/client/img/audio.svg +5 -0
  105. package/src/client/img/external-link.svg +3 -0
  106. package/src/client/img/selected-eye.svg +4 -0
  107. package/src/client/img/selected-tick.svg +3 -0
  108. package/src/client/img/thumbs/BCLeps.jpg +0 -0
  109. package/src/client/img/thumbs/Checklist.jpg +0 -0
  110. package/src/client/img/thumbs/Data-Paper-Figma.jpg +0 -0
  111. package/src/client/img/thumbs/Galiano-Data-Paper.jpg +0 -0
  112. package/src/client/img/thumbs/Galiano.jpg +0 -0
  113. package/src/client/img/thumbs/Maxwell.jpg +0 -0
  114. package/src/client/img/thumbs/Squamish.jpg +0 -0
  115. package/src/client/img/thumbs/Valdes.jpg +0 -0
  116. package/src/client/img/thumbs/Xetthecum.jpg +0 -0
  117. package/src/client/img/up-arrow.svg +4 -0
  118. package/src/client/js/autocomplete.js +53 -0
  119. package/src/client/js/checklist.js +179 -0
  120. package/src/client/js/colour.js +139 -0
  121. package/src/client/js/datasetControls.js +212 -0
  122. package/src/client/js/imerss-viz.js +1397 -0
  123. package/src/client/js/leafletMap.js +357 -0
  124. package/src/client/js/leafletMapWithGrid.js +337 -0
  125. package/src/client/js/leafletMapWithRegions.js +381 -0
  126. package/src/client/js/renderSVG.js +108 -0
  127. package/src/client/js/tabs.js +53 -0
  128. package/src/client/js/xetthecum.js +106 -0
  129. package/src/client/json/emptyPhyloMap.json +1 -0
  130. package/src/client/json/kingdomPhyloMap.json +17 -0
  131. package/src/client/json/phyloMap.json +58 -0
  132. package/src/compare.js +97 -0
  133. package/src/compilecsv.js +47 -0
  134. package/src/dataProcessing/LZ4.js +27 -0
  135. package/src/dataProcessing/coordinatePatch.js +151 -0
  136. package/src/dataProcessing/readCSV.js +107 -0
  137. package/src/dataProcessing/readCSVwithMap.js +90 -0
  138. package/src/dataProcessing/readCSVwithoutMap.js +25 -0
  139. package/src/dataProcessing/readJSON.js +23 -0
  140. package/src/dataProcessing/sqlite.js +174 -0
  141. package/src/dataProcessing/summarise.js +122 -0
  142. package/src/dataProcessing/taxonScrape.js +14 -0
  143. package/src/dataProcessing/worker.js +31 -0
  144. package/src/dataProcessing/writeCSV.js +34 -0
  145. package/src/dataProcessing/writeJSON.js +15 -0
  146. package/src/dbtaxa.js +96 -0
  147. package/src/deAuthorise.js +48 -0
  148. package/src/deqgis.js +148 -0
  149. package/src/expressions/expr.js +65 -0
  150. package/src/expressions/jsep.js +925 -0
  151. package/src/filterFirst.js +51 -0
  152. package/src/geom/geoJSON.js +121 -0
  153. package/src/iNaturalist/examples/api.inaturalist.org-v1-taxa!q=Acmispon americanus americanus.json +200 -0
  154. package/src/iNaturalist/examples/api.inaturalist.org-v1-taxa!q=Flabellina japonica.json +61 -0
  155. package/src/iNaturalist/examples/www.inaturalist.org-taxa-877359.json +132 -0
  156. package/src/iNaturalist/iNatUrls.js +17 -0
  157. package/src/iNaturalist/obsAPI.js +150 -0
  158. package/src/iNaturalist/taxonAPI.js +460 -0
  159. package/src/inatObs.js +120 -0
  160. package/src/inattify.js +123 -0
  161. package/src/intersect.js +80 -0
  162. package/src/intersectFilter.js +48 -0
  163. package/src/landlocked.js +137 -0
  164. package/src/lib/jquery.binarytransport.js +51 -0
  165. package/src/lib/lz4-commentary.txt +9 -0
  166. package/src/lib/lz4.js +73 -0
  167. package/src/lib/point-in-polygon.js +36 -0
  168. package/src/lz4.js +40 -0
  169. package/src/marmalise.js +333 -0
  170. package/src/materialise.js +68 -0
  171. package/src/reintegrate.js +71 -0
  172. package/src/taxonomise.js +615 -0
  173. package/src/unscrewGBIF.js +44 -0
  174. package/src/utils/dataSource.js +173 -0
  175. package/src/utils/settleStructure.js +62 -0
  176. package/src/utils/utils.js +92 -0
  177. package/src/viz/plethify.js +39 -0
  178. package/src/wormify.js +154 -0
  179. package/src/xetthecum/xetthecumFilters.js +126 -0
  180. package/test/testDataSource.js +182 -0
  181. package/test/testiNatObsSource.js +38 -0
  182. package/test/testiNatSource.js +50 -0
@@ -0,0 +1,58 @@
1
+ {
2
+ "kingdom:Animalia": {
3
+ "pic": "%resourceBase/img/Animalia_2.svg",
4
+ "colour": "#f26522"
5
+ },
6
+ "kingdom:Chromista": {
7
+ "pic": "%resourceBase/img/Chromista.svg",
8
+ "colour": "#009fd3"
9
+ },
10
+ "kingdom:Plantae": {
11
+ "pic": "%resourceBase/img/Plantae_2.svg",
12
+ "colour": "#8cc63f"
13
+ },
14
+ "kingdom:Protozoa": {
15
+ "pic": "%resourceBase/img/Protista.svg",
16
+ "colour": "#1eb28f"
17
+ },
18
+ "phylum:Annelida": {
19
+ "pic": "%resourceBase/img/Animalia_Annelida.svg",
20
+ "colour": "#f26522"
21
+ },
22
+ "phylum:Chaetognatha": {
23
+ "pic": "%resourceBase/img/Animalia_Chaetognatha.svg",
24
+ "colour": "#f26522"
25
+ },
26
+ "phylum:Cnidaria": {
27
+ "pic": "%resourceBase/img/Animalia_Cnidaria.svg",
28
+ "colour": "#f26522"
29
+ },
30
+ "phylum:Ctenophora": {
31
+ "pic": "%resourceBase/img/Animalia_Ctenophora.svg",
32
+ "colour": "#f26522"
33
+ },
34
+ "phylum:Nemertea": {
35
+ "pic": "%resourceBase/img/Animalia_Nemertea.svg",
36
+ "colour": "#f26522"
37
+ },
38
+ "phylum:Phoronida": {
39
+ "pic": "%resourceBase/img/Animalia_Phoronida.svg",
40
+ "colour": "#f26522"
41
+ },
42
+ "phylum:Porifera": {
43
+ "pic": "%resourceBase/img/Animalia_Porifera.svg",
44
+ "colour": "#f26522"
45
+ },
46
+ "phylum:Platyhelminthes": {
47
+ "pic": "%resourceBase/img/Animalia_Platyhelminthes.svg",
48
+ "colour": "#f26522"
49
+ },
50
+ "phylum:Sipuncula": {
51
+ "pic": "%resourceBase/img/Animalia_Sipuncula.svg",
52
+ "colour": "#f26522"
53
+ },
54
+ "life:Life": {
55
+ "taxonPic": "%resourceBase/img/Plankton.jpg",
56
+ "taxonPicDescription": "<em>\"The Importance of Plankton\"</em> by Susan Taylor"
57
+ }
58
+ }
package/src/compare.js ADDED
@@ -0,0 +1,97 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ var fluid = require("infusion");
6
+ var minimist = require("minimist");
7
+ fluid.require("%imerss-bioinfo");
8
+
9
+ require("./dataProcessing/readJSON.js");
10
+ require("./dataProcessing/readCSV.js");
11
+ require("./dataProcessing/readCSVwithMap.js");
12
+ require("./dataProcessing/writeCSV.js");
13
+
14
+ var hortis = fluid.registerNamespace("hortis");
15
+
16
+ hortis.ranks = fluid.freezeRecursive(require("../data/ranks.json"));
17
+ // Map of taxon name to list of higher taxa
18
+ hortis.higherTaxa = fluid.transform(fluid.arrayToHash(hortis.ranks), function (troo, rank) {
19
+ return hortis.ranks.slice(0, hortis.ranks.indexOf(rank));
20
+ });
21
+
22
+ fluid.setLogging(true);
23
+
24
+ var parsedArgs = minimist(process.argv.slice(2));
25
+
26
+ var file1 = parsedArgs._[0];
27
+ var file2 = parsedArgs._[1];
28
+
29
+ var mapFile = parsedArgs.map || "%imerss-bioinfo/data/coreOutMap.json";
30
+ var map = hortis.readJSONSync(fluid.module.resolvePath(mapFile), "reading map file");
31
+
32
+ var files = [file1, file2];
33
+
34
+ var readers = files.map(function (oneFile) {
35
+ return hortis.csvReaderWithMap({
36
+ inputFile: oneFile,
37
+ mapColumns: map.columns
38
+ });
39
+ });
40
+
41
+ var promises = fluid.getMembers(readers, "completionPromise");
42
+
43
+ hortis.findExcess = function (hash1, hash2) {
44
+ var togo = [];
45
+ fluid.each(hash1, function (value, name) {
46
+ if (!hash2[name]) {
47
+ togo.push(name);
48
+ }
49
+ });
50
+ return togo;
51
+ };
52
+
53
+ hortis.reportExcess = function (names, hash, mapColumns, filename) {
54
+ var excessRowHash = fluid.filterKeys(hash, names);
55
+ var excessRows = Object.values(excessRowHash);
56
+ hortis.writeCSV(filename, map.columns, excessRows, fluid.promise());
57
+ };
58
+
59
+ hortis.castOutOneHigherTaxa = function (hash, row, rank) {
60
+ if (row[rank]) {
61
+ hortis.higherTaxa[rank].forEach(function (higherRank) {
62
+ delete hash[row[higherRank]];
63
+ });
64
+ }
65
+ };
66
+
67
+ hortis.castOutHigherTaxa = function (hash) {
68
+ console.log("Original size: " + Object.keys(hash).length);
69
+ fluid.each(hash, function (row) {
70
+ hortis.castOutOneHigherTaxa(hash, row, "species");
71
+ hortis.castOutOneHigherTaxa(hash, row, "genus");
72
+ });
73
+ console.log("After casting out higher taxa: " + Object.keys(hash).length);
74
+ };
75
+
76
+ fluid.promise.sequence(promises).then(function () {
77
+ console.log("Loaded " + readers.length + " CSV files");
78
+ var rows = fluid.getMembers(readers, "rows");
79
+ var names = rows.map(function (oneRows) {
80
+ return fluid.getMembers(oneRows, "iNaturalistTaxonName");
81
+ });
82
+ var hashes = names.map(function (oneNames, index) {
83
+ var togo = {};
84
+ oneNames.forEach(function (name, rowIndex) {
85
+ togo[name] = rows[index][rowIndex];
86
+ });
87
+ return togo;
88
+ });
89
+ hashes.forEach(hortis.castOutHigherTaxa);
90
+ var onlyOne = hortis.findExcess(hashes[0], hashes[1]);
91
+ hortis.reportExcess(onlyOne, hashes[0], map.columns, "excess1.csv");
92
+ var onlyTwo = hortis.findExcess(hashes[1], hashes[0]);
93
+ hortis.reportExcess(onlyTwo, hashes[1], map.columns, "excess2.csv");
94
+
95
+ }, function (err) {
96
+ console.log("Error ", err);
97
+ });
@@ -0,0 +1,47 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ var fluid = require("infusion");
6
+ var minimist = require("minimist");
7
+ var glob = require("glob");
8
+ fluid.require("%imerss-bioinfo");
9
+
10
+ require("./dataProcessing/readJSON.js");
11
+ require("./dataProcessing/readCSV.js");
12
+ require("./dataProcessing/readCSVwithMap.js");
13
+ require("./dataProcessing/writeCSV.js");
14
+
15
+ var hortis = fluid.registerNamespace("hortis");
16
+
17
+ fluid.setLogging(true);
18
+
19
+ var parsedArgs = minimist(process.argv.slice(2));
20
+
21
+ var outputFile = parsedArgs.o || "%imerss-bioinfo/data/dataPaper-I-in/Animalia.csv";
22
+ var inputFile = parsedArgs._[0] || "%imerss-bioinfo/data/dataPaper-I-in/Animalia/*.csv";
23
+ var mapFile = parsedArgs.map || "%imerss-bioinfo/data/dataPaper-I-in/dataPaper-map.json";
24
+
25
+ var map = hortis.readJSONSync(fluid.module.resolvePath(mapFile), "reading map file");
26
+
27
+ var files = glob.sync(fluid.module.resolvePath(inputFile));
28
+
29
+ var readers = files.map(function (oneFile) {
30
+ // TODO: Worry if the schemas of the CSV files diverge - we stopped using the map file since we don't want
31
+ // its conversion, but we do want to verify the headers
32
+ // We probably want to grab the "onHeaders.validateHeaders" listener from csvReaderWithMap
33
+ return hortis.csvReaderWithoutMap({
34
+ inputFile: oneFile
35
+ });
36
+ });
37
+
38
+ var promises = fluid.getMembers(readers, "completionPromise");
39
+
40
+ fluid.promise.sequence(promises).then(function () {
41
+ console.log("Loaded " + readers.length + " CSV files");
42
+ var allRows = fluid.flatten(fluid.getMembers(readers, "rows"));
43
+ var togo = fluid.promise();
44
+ hortis.writeCSV(fluid.module.resolvePath(outputFile), Object.keys(allRows[0]), allRows, togo);
45
+ }, function (err) {
46
+ console.log("Error ", err);
47
+ });
@@ -0,0 +1,27 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ const fluid = require("infusion");
6
+ const lz4 = require("lz4");
7
+ const fs = require("fs");
8
+ const stream = require("stream");
9
+
10
+ const hortis = fluid.registerNamespace("hortis");
11
+
12
+ // TODO: move to pako https://github.com/nodeca/pako
13
+
14
+ hortis.writeLZ4File = function (text, filename) {
15
+ const input = new stream.Readable();
16
+ input.push(text);
17
+ input.push(null);
18
+
19
+ const output = fs.createWriteStream(filename);
20
+
21
+ const encoder = lz4.createEncoderStream();
22
+ input.pipe(encoder).pipe(output);
23
+ output.on("finish", function () {
24
+ const stats = fs.statSync(filename);
25
+ console.log("Written " + stats.size + " bytes to " + filename);
26
+ });
27
+ };
@@ -0,0 +1,151 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ var fluid = require("infusion");
6
+
7
+ var hortis = fluid.registerNamespace("hortis");
8
+
9
+ // TODO: grab this from the fusion's input map
10
+ hortis.stableDatasetIds = {
11
+ iNat: true,
12
+ CMN: true,
13
+ RBCM: true
14
+ };
15
+
16
+ // Also in leafletMap.js
17
+ hortis.datasetIdFromObs = function (obsId) {
18
+ var colpos = obsId.indexOf(":");
19
+ return obsId.substring(0, colpos);
20
+ };
21
+
22
+ hortis.equalCoordinate = function (a, b) {
23
+ // 6 decimals is a common standard - about 10cm accuracy, e.g. RBCM truncated to this
24
+ return Math.abs(a - b) < 1e-6;
25
+ };
26
+
27
+ hortis.equalCoordinates = function (lat1, long1, lat2, long2) {
28
+ return hortis.equalCoordinate(lat1, lat2) && hortis.equalCoordinate(long1, long2);
29
+ };
30
+
31
+ hortis.matchOnePatchRow = function (patchRow, obsRow) {
32
+ var score = 0;
33
+ if (patchRow.id === obsRow.observationId) {
34
+ var dataset = hortis.datasetIdFromObs(patchRow.id);
35
+ score += hortis.stableDatasetIds[dataset] ? 1 : 0.5;
36
+ }
37
+ if (hortis.equalCoordinates(patchRow.latitude, patchRow.longitude, obsRow.latitude, obsRow.longitude)) {
38
+ score += 1;
39
+ }
40
+ if (patchRow.iNaturalistTaxonName === obsRow.iNaturalistTaxonName) {
41
+ score += 1;
42
+ }
43
+ return score;
44
+ };
45
+
46
+ hortis.dumpPatchRow = function (patchRow, loc) {
47
+ return Object.values(patchRow).join(", ") + loc;
48
+ };
49
+
50
+ hortis.dumpObsMatch = function (match) {
51
+ var obs = match.obsRow;
52
+ return "obs of " + obs.iNaturalistTaxonName + " with coordinates " + JSON.stringify(obs.latitude) + ", " + JSON.stringify(obs.longitude) +
53
+ " with id " + obs.observationId + " at index " + match.obsIndex;
54
+ };
55
+
56
+ /** Returns one match or null, plus some side-effects
57
+ * @param {Object} patchRow - One row of the patch file
58
+ * @param {ObsRow[]} obsRows - All observation rows
59
+ * @param {String} loc - A diagnostic string indicating the location of the `patchRow`
60
+ * @return {ObsRow|Null} The best matching observation row, or null
61
+ */
62
+ hortis.matchCoordinatePatch = function (patchRow, obsRows, loc) {
63
+ var matches = [];
64
+ obsRows.forEach(function (obsRow, obsIndex) {
65
+ var score = hortis.matchOnePatchRow(patchRow, obsRow);
66
+ if (score > 0) {
67
+ matches.push({
68
+ score: score,
69
+ obsRow: obsRow,
70
+ obsIndex: obsIndex
71
+ });
72
+ }
73
+ });
74
+ var diag = "Warning: patch " + hortis.dumpPatchRow(patchRow, loc);
75
+ if (matches.length === 0) {
76
+ console.log(diag + " failed to match in any coordinate");
77
+ return null;
78
+ } else {
79
+ matches.sort(function (a, b) {
80
+ return b.score - a.score;
81
+ });
82
+ var topScore = matches[0].score;
83
+ var topScoring = matches.filter(function (match) {
84
+ return match.score === topScore;
85
+ });
86
+ if (topScoring.length > 1) {
87
+ console.log(diag + " matched " + topScoring.length + " rows equally well: these were: "
88
+ + topScoring.map(hortis.dumpObsMatch).join("\n"));
89
+ } else if (topScore < 2.5) {
90
+ console.log(diag + " matched " + hortis.dumpObsMatch(topScoring[0]) + " with reduced accuracy of " + topScore);
91
+ var obsRow = topScoring[0].obsRow;
92
+ console.log(hortis.equalCoordinates(patchRow.latitude, patchRow.longitude, obsRow.latitude, obsRow.longitude));
93
+ }
94
+ return topScoring[0].obsRow;
95
+ }
96
+ };
97
+
98
+ hortis.coordinatesOutMap = {
99
+ columns: {
100
+ coordinatesCorrected: "Coordinates Corrected",
101
+ coordinatesCorrectedNote: "Coordinates Corrected Note"
102
+ }
103
+ };
104
+
105
+ hortis.parseToNumber = function (row, field) {
106
+ var parsed = hortis.parseFloat(row[field]);
107
+ row[field] = parsed;
108
+ };
109
+
110
+ hortis.patchNumberFields = ["latitude", "longitude", "latitudeOut", "longitudeOut"];
111
+
112
+ /**
113
+ * Resolve a coordinate patch file against the observation list
114
+ * @param {Object} resolved - Resolved observations containing members obsOutMap and obsRows which will be modified
115
+ * @param {PatchFile} patch - The loaded patch structure
116
+ * @param {String} key - Key of the patch
117
+ */
118
+ hortis.processCoordinatePatch = function (resolved, patch, key) {
119
+
120
+ patch.patchData.rows.forEach(function (patchRow, index) {
121
+ var loc = " at row " + index + " of patch " + key;
122
+ hortis.patchNumberFields.forEach(function (field) {
123
+ hortis.parseToNumber(patchRow, field);
124
+ });
125
+ var match = hortis.matchCoordinatePatch(patchRow, resolved.obsRows, loc);
126
+
127
+ var expectCoordinates = function (testEqual) {
128
+ var equal = hortis.equalCoordinates(match.latitude, match.longitude, patchRow.latitudeOut, patchRow.longitudeOut);
129
+ if (equal !== testEqual) {
130
+ console.log("Error: Coordinate corrected value for patch " + hortis.dumpPatchRow(patchRow, loc) + " does not conform with original coordinates "
131
+ + match.latitude + ", " + match.longitude + " since value of \"Corrected\" is " + patchRow.corrected);
132
+ }
133
+ };
134
+
135
+ if (match) {
136
+ if (patchRow.corrected === "yes") {
137
+ expectCoordinates(false);
138
+ match.coordinatesCorrected = "yes";
139
+ } else if (patchRow.corrected === "no") {
140
+ expectCoordinates(true);
141
+ match.coordinatesCorrected = "no";
142
+ } else {
143
+ console.log("Unexpected \"Corrected\" value " + patchRow.corrected + " for patch " + hortis.dumpPatchRow(patchRow, loc));
144
+ }
145
+ match.latitude = patchRow.latitudeOut;
146
+ match.longitude = patchRow.longitudeOut;
147
+ match.coordinatesCorrectedNote = patchRow.correction;
148
+ }
149
+ });
150
+ resolved.combinedObsOutMap = hortis.combineMaps([resolved.combinedObsOutMap, hortis.coordinatesOutMap]);
151
+ };
@@ -0,0 +1,107 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ const fluid = require("infusion");
6
+ const csvModule = require("csv-parser");
7
+ const fs = require("fs");
8
+ const removeBOM = require("remove-bom-stream");
9
+
10
+ const hortis = fluid.registerNamespace("hortis");
11
+
12
+ fluid.defaults("hortis.csvReader", {
13
+ gradeNames: "fluid.component",
14
+ members: {
15
+ line: 1,
16
+ rowStream: null,
17
+ headers: {},
18
+ completionPromise: "@expand:fluid.promise()"
19
+ },
20
+ csvOptions: {
21
+ },
22
+ events: {
23
+ onHeaders: null,
24
+ onRow: null,
25
+ onError: null,
26
+ onCompletion: null
27
+ },
28
+ listeners: {
29
+ "onRow.lineCounter": {
30
+ funcName: "hortis.csvReader.incLine",
31
+ args: "{that}",
32
+ priority: "first"
33
+ },
34
+ "onHeaders.storeHeaders": "hortis.csvReader.storeHeaders({that}, {arguments}.0)",
35
+ "onError.rejector": "hortis.csvReader.rejector({that}, {arguments}.0)",
36
+ "onCreate.openStream": "fluid.notImplemented",
37
+ "onCreate.bindStream": {
38
+ priority: "after:openStream",
39
+ funcName: "hortis.csvReader.bindStream"
40
+ }
41
+ }
42
+ });
43
+
44
+ hortis.csvReader.incLine = function (that) {
45
+ ++that.line;
46
+ };
47
+
48
+ hortis.csvReader.storeHeaders = function (that, headers) {
49
+ that.headers = headers;
50
+ };
51
+
52
+ hortis.csvReader.rejector = function (that, error) {
53
+ that.completionPromise.reject({
54
+ message: "Error at line " + that.line + " reading file " + that.options.inputFile + ": " + error
55
+ });
56
+ };
57
+
58
+ hortis.csvReader.bindStream = function (that) {
59
+ const now = Date.now();
60
+ that.rowStream.on("data", function (data) {
61
+ that.events.onRow.fire(data);
62
+ });
63
+ that.rowStream.on("error", function (error) {
64
+ that.events.onError.fire(error);
65
+ });
66
+ that.rowStream.on("headers", function (headers) {
67
+ that.events.onHeaders.fire(headers);
68
+ });
69
+ that.rowStream.on("end", function () {
70
+ console.log("Read " + that.line + " lines in " + (Date.now() - now) + " ms");
71
+ if (!that.completionPromise.disposition) {
72
+ that.events.onCompletion.fire();
73
+ }
74
+ });
75
+ };
76
+
77
+
78
+
79
+ fluid.defaults("hortis.csvFileReader", {
80
+ gradeNames: "hortis.csvReader",
81
+ inputFile: null,
82
+ listeners: {
83
+ "onCreate.openStream": "hortis.csvFileReader.openFileStream"
84
+ }
85
+ });
86
+
87
+ hortis.csvFileReader.openFileStream = function (that) {
88
+ that.rowStream = fs.createReadStream(that.options.inputFile)
89
+ .pipe(removeBOM("utf-8"))
90
+ .pipe(csvModule(that.options.csvOptions)
91
+ );
92
+ };
93
+
94
+ fluid.defaults("hortis.storingCSVReader", {
95
+ members: {
96
+ rows: []
97
+ },
98
+ listeners: {
99
+ "onCompletion.resolve": {
100
+ func: "{that}.completionPromise.resolve",
101
+ args: {
102
+ rows: "{that}.rows",
103
+ headers: "{that}.headers"
104
+ }
105
+ }
106
+ }
107
+ });
@@ -0,0 +1,90 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ const fluid = require("infusion");
6
+ const moment = require("moment"); // Horrid - required to apply template parsing to Date fields
7
+
8
+ const hortis = fluid.registerNamespace("hortis");
9
+
10
+ fluid.defaults("hortis.csvReaderWithMap", {
11
+ gradeNames: ["hortis.csvFileReader", "hortis.storingCSVReader"],
12
+ // "columns" member of the map structure
13
+ mapColumns: null,
14
+ // true if the map file defines template interpolation via % rather than simply mapping column names e.g. as an "outMap"
15
+ templateMap: false,
16
+ listeners: {
17
+ "onHeaders.validateHeaders": {
18
+ funcName: "hortis.validateHeaders",
19
+ args: ["{that}.options.mapColumns", "{that}.options.templateMap", "{that}.headers", "{that}.events.onError"]
20
+ },
21
+ "onRow.storeRow": "hortis.storeRow({that}, {arguments}.0)"
22
+ }
23
+ });
24
+
25
+ hortis.templateMapField = function (that, data, template) {
26
+ if (template.startsWith("!Date")) {
27
+ const match = template.match(/!Date\((.*),(.*)\)/);
28
+ const format = match[1], smallTemplate = match[2];
29
+ const value = fluid.stringTemplate(smallTemplate, data);
30
+ return moment.utc(value, format).toISOString();
31
+ } else if (template.startsWith("@")) {
32
+ const rec = fluid.compactStringToRec(template.substring(1), "expander");
33
+ const func = fluid.getGlobalValue(rec.funcName);
34
+ if (!func) {
35
+ fluid.fail("Unable to look up " + rec.funcName + " as a global function");
36
+ }
37
+ const args = fluid.transform(rec.args, function (arg) {
38
+ return fluid.stringTemplate(arg, data);
39
+ });
40
+ return func.apply(null, args);
41
+ } else {
42
+ const warnFunc = function (key) {
43
+ console.log("Warning: template key %" + key + " did not match a column in the data for file " + that.options.inputFile);
44
+ };
45
+ const togo = fluid.stringTemplateWarn(template, data, warnFunc);
46
+ return togo;
47
+ }
48
+ };
49
+
50
+ // Speed on taxa - 500ms on IoC dispatch, 1300ms on mapping
51
+ hortis.mapRow = function (that, data, mapColumns, templateMap, index) {
52
+ const togo = {};
53
+ fluid.each(mapColumns, function (label, key) {
54
+ togo[key] = templateMap ? hortis.templateMapField(that, data, label) : data[label];
55
+ });
56
+ togo.index = index;
57
+ return togo;
58
+ };
59
+
60
+ hortis.storeRow = function (that, row) {
61
+ const mappedRow = hortis.mapRow(that, row, that.options.mapColumns, that.options.templateMap, that.rows.length + 1);
62
+ that.rows.push(mappedRow);
63
+ };
64
+
65
+ hortis.validateHeaders = function (mapColumns, templateMap, headers, onError) {
66
+ if (!mapColumns || mapColumns.length === 0) {
67
+ fluid.fail("Error in csvReaderWithMap - no mapColumns were supplied");
68
+ }
69
+ const headerHash = fluid.arrayToHash(headers);
70
+ fluid.each(mapColumns, function (label) {
71
+ if (templateMap) {
72
+ const interp = fluid.stringTemplate(label, headerHash);
73
+ if (interp.indexOf("%") !== -1) {
74
+ onError.fire("Error in headers - template " + label + " did not match headers");
75
+ }
76
+ } else {
77
+ if (headers.indexOf(label) === -1) {
78
+ onError.fire("Error in headers - field " + label + " required in map file was not found");
79
+ }
80
+ }
81
+ });
82
+ };
83
+
84
+ // Used in writeCSV
85
+ hortis.mapToArray = function (row, map) {
86
+ const keys = Object.keys(map);
87
+ return keys.map(function (header) {
88
+ return row[header];
89
+ });
90
+ };
@@ -0,0 +1,25 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ var fluid = require("infusion");
6
+
7
+ var hortis = fluid.registerNamespace("hortis");
8
+
9
+ fluid.defaults("hortis.csvReaderWithoutMap", {
10
+ gradeNames: ["hortis.csvFileReader", "hortis.storingCSVReader"],
11
+ listeners: {
12
+ "onRow.storeRow": "hortis.storeRowWithoutMap({that}, {arguments}.0)"
13
+ }
14
+ });
15
+
16
+ hortis.storeRowWithoutMap = function (that, row) {
17
+ that.rows.push(row);
18
+ };
19
+
20
+ // Used in writeCSV
21
+ hortis.rowToArray = function (row, headers) {
22
+ return headers.map(function (header) {
23
+ return row[header];
24
+ });
25
+ };
@@ -0,0 +1,23 @@
1
+ /* eslint-env node */
2
+
3
+ "use strict";
4
+
5
+ const fluid = require("infusion");
6
+ const kettle = require("kettle");
7
+
8
+ const hortis = fluid.registerNamespace("hortis");
9
+
10
+ hortis.readJSONSync = function (fileName, message) {
11
+ const promise = kettle.JSON.readFileSync(fileName, message + " " + fileName);
12
+ let togo;
13
+ promise.then(function (parsed) {
14
+ togo = parsed;
15
+ }, function (err) {
16
+ throw err;
17
+ });
18
+ return togo;
19
+ };
20
+
21
+ hortis.readModuleJSONSync = function (fileName, message) {
22
+ return hortis.readJSONSync(fluid.module.resolvePath(fileName), message);
23
+ };