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.
- package/README.md +156 -0
- package/build.js +196 -0
- package/docs/css/imerss-viz-all.css +1227 -0
- package/docs/css/imerss-viz-wp-overrides.css +70 -0
- package/docs/css/pepiowelh.css +76 -0
- package/docs/css/xetthecum-external.css +47 -0
- package/docs/css/xetthecum-shared.css +80 -0
- package/docs/css/xetthecum.css +526 -0
- package/docs/js/imerss-viz-all.js +58 -0
- package/docs/js/imerss-viz-all.js.map +1 -0
- package/docs/js/jquery.js +10881 -0
- package/index.html +81 -0
- package/index.js +7 -0
- package/indexWithJustMap.html +92 -0
- package/indexWithMap.html +79 -0
- package/indexWithMapHoweSound.html +91 -0
- package/indexWithMapPepiowelh.html +119 -0
- package/indexWithMapValdes.html +90 -0
- package/indexWithMapXetthecum.html +90 -0
- package/package.json +50 -0
- package/src/WoRMS/taxonAPI.js +13 -0
- package/src/arphify.js +662 -0
- package/src/assignNames.js +103 -0
- package/src/auxBuild/restoreJQuery.js +1 -0
- package/src/buildSource/Comprehensive Lists.html +42 -0
- package/src/buildSource/Data Paper Part I Visualisation.html +50 -0
- package/src/buildSource/Galiano Life List.html +43 -0
- package/src/buildSource/Metchosin/index.html +22 -0
- package/src/buildSource/Pe'pi'ow'elh.html +72 -0
- package/src/buildSource/Squamish Life List.html +48 -0
- package/src/buildSource/Valdes Island Biodiversity.html +64 -0
- package/src/buildSource/Xetthecum.html +40 -0
- package/src/buildSource/dataPaperSunburstAndMap.html +30 -0
- package/src/buildSource/index.html +224 -0
- package/src/client/css/imerss-viz-wp-overrides.css +70 -0
- package/src/client/css/imerss-viz.css +531 -0
- package/src/client/css/pepiowelh.css +76 -0
- package/src/client/css/xetthecum-external.css +47 -0
- package/src/client/css/xetthecum-shared.css +80 -0
- package/src/client/css/xetthecum.css +526 -0
- package/src/client/html/imerss-viz-map-only.html +75 -0
- package/src/client/html/imerss-viz-map-pepi.html +77 -0
- package/src/client/html/imerss-viz-map-xetthecum.html +90 -0
- package/src/client/html/imerss-viz-map.html +75 -0
- package/src/client/html/imerss-viz.html +58 -0
- package/src/client/html/importing Xetthecum patterns.txt +10 -0
- package/src/client/html/xetthecum-patterns.html +27 -0
- package/src/client/img/Animalia_2.svg +1 -0
- package/src/client/img/Animalia_Annelida.svg +655 -0
- package/src/client/img/Animalia_Chaetognatha.svg +72 -0
- package/src/client/img/Animalia_Cnidaria.svg +173 -0
- package/src/client/img/Animalia_Ctenophora.svg +298 -0
- package/src/client/img/Animalia_Nemertea.svg +54 -0
- package/src/client/img/Animalia_Phoronida.svg +271 -0
- package/src/client/img/Animalia_Platyhelminthes.svg +58 -0
- package/src/client/img/Animalia_Porifera.svg +220 -0
- package/src/client/img/Animalia_Sipuncula.svg +155 -0
- package/src/client/img/Arachnoidiscus_schmidtii_web_1-2-scaled.webp +0 -0
- package/src/client/img/Arrow-down.svg +11 -0
- package/src/client/img/Arrow-up.svg +6 -0
- package/src/client/img/Chromista.svg +1 -0
- package/src/client/img/Pepiowelh/BlackNoWe2-wb-bb.png +0 -0
- package/src/client/img/Pepiowelh/BlackNoWe2-wb.png +0 -0
- package/src/client/img/Pepiowelh/BlackNoWe2.png +0 -0
- package/src/client/img/Pepiowelh/Ecocultural Mapping Banner.svg +1 -0
- package/src/client/img/Plankton.jpg +0 -0
- package/src/client/img/Plantae.svg +1 -0
- package/src/client/img/Plantae_2.svg +1 -0
- package/src/client/img/Protista.svg +1 -0
- package/src/client/img/Valdes/Valdes Island Conservancy.jpg +0 -0
- package/src/client/img/Xetthecum/Agricultural.jpg +0 -0
- package/src/client/img/Xetthecum/Anthropogenic_banner.jpg +0 -0
- package/src/client/img/Xetthecum/Broad_Leaf.jpg +0 -0
- package/src/client/img/Xetthecum/Caves.jpg +0 -0
- package/src/client/img/Xetthecum/Coastal_Bluffs.jpg +0 -0
- package/src/client/img/Xetthecum/Conifer_Pole_Sapling.jpg +0 -0
- package/src/client/img/Xetthecum/Developed.jpg +0 -0
- package/src/client/img/Xetthecum/Dock.jpg +0 -0
- package/src/client/img/Xetthecum/Eelgrass.jpg +0 -0
- package/src/client/img/Xetthecum/Forests_banner.jpg +0 -0
- package/src/client/img/Xetthecum/Freshwater_banner.jpg +0 -0
- package/src/client/img/Xetthecum/Greig_Creek.jpg +0 -0
- package/src/client/img/Xetthecum/Intertidal.jpg +0 -0
- package/src/client/img/Xetthecum/Laughlin_Lake.jpg +0 -0
- package/src/client/img/Xetthecum/Marine_banner.jpg +0 -0
- package/src/client/img/Xetthecum/Mature_Conifer_Forest.jpg +0 -0
- package/src/client/img/Xetthecum/Porlier_Pass_Road.jpg +0 -0
- package/src/client/img/Xetthecum/Riparian.jpg +0 -0
- package/src/client/img/Xetthecum/Rural.jpg +0 -0
- package/src/client/img/Xetthecum/Subtidal.jpg +0 -0
- package/src/client/img/Xetthecum/Wetland.jpg +0 -0
- package/src/client/img/Xetthecum/Woodland.jpg +0 -0
- package/src/client/img/Xetthecum/Woodland_banner.jpg +0 -0
- package/src/client/img/Xetthecum/Xetthecum.jpg +0 -0
- package/src/client/img/Xetthecum/Young_Conifer_Forest.jpg +0 -0
- package/src/client/img/Xetthecum/Young_Mixed_Forest.jpg +0 -0
- package/src/client/img/Xetthecum/food.svg +38 -0
- package/src/client/img/Xetthecum/indicator.svg +52 -0
- package/src/client/img/Xetthecum/material.svg +186 -0
- package/src/client/img/Xetthecum/medicinal.svg +62 -0
- package/src/client/img/Xetthecum/missing-value.svg +3 -0
- package/src/client/img/Xetthecum/spiritual.svg +13 -0
- package/src/client/img/Xetthecum/trade.svg +74 -0
- package/src/client/img/audio.svg +5 -0
- package/src/client/img/external-link.svg +3 -0
- package/src/client/img/selected-eye.svg +4 -0
- package/src/client/img/selected-tick.svg +3 -0
- package/src/client/img/thumbs/BCLeps.jpg +0 -0
- package/src/client/img/thumbs/Checklist.jpg +0 -0
- package/src/client/img/thumbs/Data-Paper-Figma.jpg +0 -0
- package/src/client/img/thumbs/Galiano-Data-Paper.jpg +0 -0
- package/src/client/img/thumbs/Galiano.jpg +0 -0
- package/src/client/img/thumbs/Maxwell.jpg +0 -0
- package/src/client/img/thumbs/Squamish.jpg +0 -0
- package/src/client/img/thumbs/Valdes.jpg +0 -0
- package/src/client/img/thumbs/Xetthecum.jpg +0 -0
- package/src/client/img/up-arrow.svg +4 -0
- package/src/client/js/autocomplete.js +53 -0
- package/src/client/js/checklist.js +179 -0
- package/src/client/js/colour.js +139 -0
- package/src/client/js/datasetControls.js +212 -0
- package/src/client/js/imerss-viz.js +1397 -0
- package/src/client/js/leafletMap.js +357 -0
- package/src/client/js/leafletMapWithGrid.js +337 -0
- package/src/client/js/leafletMapWithRegions.js +381 -0
- package/src/client/js/renderSVG.js +108 -0
- package/src/client/js/tabs.js +53 -0
- package/src/client/js/xetthecum.js +106 -0
- package/src/client/json/emptyPhyloMap.json +1 -0
- package/src/client/json/kingdomPhyloMap.json +17 -0
- package/src/client/json/phyloMap.json +58 -0
- package/src/compare.js +97 -0
- package/src/compilecsv.js +47 -0
- package/src/dataProcessing/LZ4.js +27 -0
- package/src/dataProcessing/coordinatePatch.js +151 -0
- package/src/dataProcessing/readCSV.js +107 -0
- package/src/dataProcessing/readCSVwithMap.js +90 -0
- package/src/dataProcessing/readCSVwithoutMap.js +25 -0
- package/src/dataProcessing/readJSON.js +23 -0
- package/src/dataProcessing/sqlite.js +174 -0
- package/src/dataProcessing/summarise.js +122 -0
- package/src/dataProcessing/taxonScrape.js +14 -0
- package/src/dataProcessing/worker.js +31 -0
- package/src/dataProcessing/writeCSV.js +34 -0
- package/src/dataProcessing/writeJSON.js +15 -0
- package/src/dbtaxa.js +96 -0
- package/src/deAuthorise.js +48 -0
- package/src/deqgis.js +148 -0
- package/src/expressions/expr.js +65 -0
- package/src/expressions/jsep.js +925 -0
- package/src/filterFirst.js +51 -0
- package/src/geom/geoJSON.js +121 -0
- package/src/iNaturalist/examples/api.inaturalist.org-v1-taxa!q=Acmispon americanus americanus.json +200 -0
- package/src/iNaturalist/examples/api.inaturalist.org-v1-taxa!q=Flabellina japonica.json +61 -0
- package/src/iNaturalist/examples/www.inaturalist.org-taxa-877359.json +132 -0
- package/src/iNaturalist/iNatUrls.js +17 -0
- package/src/iNaturalist/obsAPI.js +150 -0
- package/src/iNaturalist/taxonAPI.js +460 -0
- package/src/inatObs.js +120 -0
- package/src/inattify.js +123 -0
- package/src/intersect.js +80 -0
- package/src/intersectFilter.js +48 -0
- package/src/landlocked.js +137 -0
- package/src/lib/jquery.binarytransport.js +51 -0
- package/src/lib/lz4-commentary.txt +9 -0
- package/src/lib/lz4.js +73 -0
- package/src/lib/point-in-polygon.js +36 -0
- package/src/lz4.js +40 -0
- package/src/marmalise.js +333 -0
- package/src/materialise.js +68 -0
- package/src/reintegrate.js +71 -0
- package/src/taxonomise.js +615 -0
- package/src/unscrewGBIF.js +44 -0
- package/src/utils/dataSource.js +173 -0
- package/src/utils/settleStructure.js +62 -0
- package/src/utils/utils.js +92 -0
- package/src/viz/plethify.js +39 -0
- package/src/wormify.js +154 -0
- package/src/xetthecum/xetthecumFilters.js +126 -0
- package/test/testDataSource.js +182 -0
- package/test/testiNatObsSource.js +38 -0
- package/test/testiNatSource.js +50 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const fluid = require("infusion");
|
|
6
|
+
|
|
7
|
+
fluid.defaults("fluid.dataSource.noencoding", {
|
|
8
|
+
components: {
|
|
9
|
+
encoding: {
|
|
10
|
+
type: "fluid.dataSource.encoding.none"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// Patch this from Kettle DataSource to interpret iNat errors which have
|
|
16
|
+
fluid.extractHtmlError = function (received) {
|
|
17
|
+
const preMatches = /<pre>(.*)<\/pre>/gm.exec(received);
|
|
18
|
+
if (preMatches) {
|
|
19
|
+
return preMatches[1];
|
|
20
|
+
}
|
|
21
|
+
const titleMatches = /<title>(.*)<\/title>/gm.exec(received);
|
|
22
|
+
return titleMatches ? titleMatches[1] : received;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
fluid.defaults("fluid.dataSource.retrying", {
|
|
26
|
+
gradeNames: "fluid.dataSource",
|
|
27
|
+
retryInterval: 1000,
|
|
28
|
+
retries: 3,
|
|
29
|
+
invokers: {
|
|
30
|
+
get: {
|
|
31
|
+
funcName: "fluid.dataSource.retrying.get",
|
|
32
|
+
args: ["{that}", "{arguments}.0", "{arguments}.1"] // directModel, directOptions
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
fluid.promise.delay = function (delay) {
|
|
38
|
+
const togo = fluid.promise();
|
|
39
|
+
setTimeout(togo.resolve, delay);
|
|
40
|
+
return togo;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
fluid.dataSource.retrying.get = async function (that, directModel, directOptions) {
|
|
44
|
+
const underlyingGet = function () {
|
|
45
|
+
return fluid.dataSource.get(that, directModel, directOptions);
|
|
46
|
+
};
|
|
47
|
+
let err = null;
|
|
48
|
+
for (let i = 0; i < that.options.retries; ++i) {
|
|
49
|
+
try {
|
|
50
|
+
const payload = await underlyingGet();
|
|
51
|
+
return payload;
|
|
52
|
+
} catch (e) {
|
|
53
|
+
fluid.log("Got exception ", e.message , " from dataSource, retry " + (i + 1) + "/" + that.options.retries);
|
|
54
|
+
err = e;
|
|
55
|
+
await fluid.promise.delay(that.options.retryInterval);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
throw err;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
fluid.setLogging(true);
|
|
62
|
+
|
|
63
|
+
// A rate limited dataSource that will guarantee not to make more than 1 call every <rateLimit> ms to the underlying
|
|
64
|
+
// dataSource
|
|
65
|
+
|
|
66
|
+
// Neither throttle nor debounce seem appropriate for this - https://underscorejs.org/#throttle - since they do
|
|
67
|
+
// not guarantee one for one invocation of target source
|
|
68
|
+
|
|
69
|
+
fluid.defaults("fluid.dataSource.rateLimiter", {
|
|
70
|
+
gradeNames: "fluid.component",
|
|
71
|
+
// minimum time in ms between calls
|
|
72
|
+
rateLimit: 1000,
|
|
73
|
+
members: {
|
|
74
|
+
pending: null,
|
|
75
|
+
lastFired: 0,
|
|
76
|
+
queue: []
|
|
77
|
+
},
|
|
78
|
+
invokers: {
|
|
79
|
+
requestStart: "fluid.dataSource.rateLimiter.requestStart({that}, {arguments}.0)",
|
|
80
|
+
requestEnd: "fluid.dataSource.rateLimiter.requestEnd({that}, {arguments}.0)",
|
|
81
|
+
next: "fluid.dataSource.rateLimiter.next({that})"
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
fluid.dataSource.rateLimiter.next = function (that) {
|
|
86
|
+
const now = Date.now();
|
|
87
|
+
const resolve = function (toResolve) {
|
|
88
|
+
that.lastFired = now;
|
|
89
|
+
toResolve.resolve(toResolve.payload);
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const uncess = that.lastFired + that.options.rateLimit - now;
|
|
93
|
+
// console.log("rateLimit: " + that.options.rateLimit + " uncess " + uncess);
|
|
94
|
+
if (!that.pending && that.queue.length > 0) {
|
|
95
|
+
if (uncess > 0) {
|
|
96
|
+
that.pending = setTimeout(function () {
|
|
97
|
+
that.pending = null;
|
|
98
|
+
that.next();
|
|
99
|
+
}, uncess);
|
|
100
|
+
} else {
|
|
101
|
+
const top = that.queue.shift();
|
|
102
|
+
resolve(top);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
fluid.dataSource.rateLimiter.requestStart = function (that, payload) {
|
|
108
|
+
const togo = fluid.promise();
|
|
109
|
+
togo.payload = payload;
|
|
110
|
+
that.queue.push(togo);
|
|
111
|
+
that.next();
|
|
112
|
+
return togo;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
fluid.dataSource.rateLimiter.requestEnd = function (that, payload) {
|
|
116
|
+
// fluid.log("requestEnd");
|
|
117
|
+
that.next();
|
|
118
|
+
return payload;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
fluid.defaults("fluid.dataSource.withRateLimiter", {
|
|
122
|
+
gradeNames: "fluid.dataSource.rateLimiter",
|
|
123
|
+
listeners: {
|
|
124
|
+
"onRead.rateLimitBefore": {
|
|
125
|
+
priority: "before:impl",
|
|
126
|
+
func: "{that}.requestStart"
|
|
127
|
+
},
|
|
128
|
+
"onRead.rateLimitAfter": {
|
|
129
|
+
priority: "after:impl",
|
|
130
|
+
func: "{that}.requestEnd"
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
fluid.defaults("fluid.inMemoryCachedSource", {
|
|
136
|
+
gradeNames: ["fluid.dataSource", "fluid.dataSource.noencoding", "fluid.dataSource.writable"],
|
|
137
|
+
members: {
|
|
138
|
+
cache: {}
|
|
139
|
+
},
|
|
140
|
+
listeners: {
|
|
141
|
+
"onRead.impl": {
|
|
142
|
+
func: "fluid.inMemoryCachedSource.read",
|
|
143
|
+
args: ["{that}", "{arguments}.0", "{arguments}.1"]
|
|
144
|
+
},
|
|
145
|
+
"onWrite.impl": {
|
|
146
|
+
func: "fluid.inMemoryCachedSource.write",
|
|
147
|
+
args: ["{that}", "{arguments}.0", "{arguments}.1"]
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
fluid.inMemoryCachedSource.toCacheKey = function (directModel) {
|
|
153
|
+
if (fluid.isPrimitive(directModel)) {
|
|
154
|
+
return directModel.toString();
|
|
155
|
+
} else {
|
|
156
|
+
let togo = "";
|
|
157
|
+
for (const key in directModel) {
|
|
158
|
+
togo += key + "|" + directModel[key] + "|";
|
|
159
|
+
}
|
|
160
|
+
return togo;
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
fluid.inMemoryCachedSource.read = function (that, payload, options) {
|
|
165
|
+
const key = fluid.inMemoryCachedSource.toCacheKey(options.directModel);
|
|
166
|
+
return that.cache[key];
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
fluid.inMemoryCachedSource.write = function (that, payload, options) {
|
|
170
|
+
const key = fluid.inMemoryCachedSource.toCacheKey(options.directModel);
|
|
171
|
+
that.cache[key] = payload;
|
|
172
|
+
return payload;
|
|
173
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
var fluid = require("infusion");
|
|
6
|
+
var hortis = fluid.registerNamespace("hortis");
|
|
7
|
+
|
|
8
|
+
hortis.settleStructure = function (structure) {
|
|
9
|
+
if (fluid.isPromise(structure)) {
|
|
10
|
+
return structure;
|
|
11
|
+
}
|
|
12
|
+
var settleRec = {
|
|
13
|
+
// This flag is an awkward consequence of our choice to allow synchronous promise resolution
|
|
14
|
+
inSync: true, // Are we still in synchronous scanning - necessary to avoid double resolution on hitting unresolved === 0 on initial scan
|
|
15
|
+
unresolved: 0,
|
|
16
|
+
depth: 0,
|
|
17
|
+
promise: fluid.promise(),
|
|
18
|
+
structure: structure,
|
|
19
|
+
resolve: function () {
|
|
20
|
+
settleRec.promise.resolve(structure);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
hortis.settleStructureRecurse(structure, settleRec);
|
|
24
|
+
settleRec.inSync = false;
|
|
25
|
+
if (settleRec.unresolved === 0) { // Case of 0 asynchronous promises found
|
|
26
|
+
settleRec.resolve();
|
|
27
|
+
}
|
|
28
|
+
return settleRec.promise;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
hortis.settleStructureRecurse = function (structure, settleRec) {
|
|
32
|
+
++settleRec.depth;
|
|
33
|
+
if (settleRec.depth > fluid.strategyRecursionBailout) {
|
|
34
|
+
fluid.fail("Recursion exceeded for value " + JSON.stringify(structure) + " overall structure " + JSON.stringify(settleRec.structure, null, 2));
|
|
35
|
+
}
|
|
36
|
+
if (fluid.isPlainObject(structure)) {
|
|
37
|
+
fluid.each(structure, function (value, key) {
|
|
38
|
+
if (fluid.isPromise(value)) {
|
|
39
|
+
hortis.settleStructurePush(settleRec, structure, value, key);
|
|
40
|
+
} else {
|
|
41
|
+
hortis.settleStructureRecurse(value, settleRec);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
--settleRec.depth;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
hortis.settleStructurePush = function (settleRec, holder, promise, key) {
|
|
49
|
+
++settleRec.unresolved;
|
|
50
|
+
promise.then(function (value) {
|
|
51
|
+
holder[key] = value;
|
|
52
|
+
--settleRec.unresolved;
|
|
53
|
+
if (settleRec.unresolved === 0 && !settleRec.inSync) {
|
|
54
|
+
settleRec.resolve();
|
|
55
|
+
}
|
|
56
|
+
}, function (err) {
|
|
57
|
+
if (!settleRec.promise.disposition) {
|
|
58
|
+
settleRec.promise.reject(hortis.upgradeError(err, " while resolving promise with key " + key));
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
const fluid = require("infusion");
|
|
6
|
+
|
|
7
|
+
const hortis = fluid.registerNamespace("hortis");
|
|
8
|
+
|
|
9
|
+
hortis.capitalize = function (string) {
|
|
10
|
+
return string.charAt(0).toUpperCase() + string.slice(1);
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
hortis.pluralise = function (number, noun) {
|
|
14
|
+
return number + " " + noun + (number === 1 ? "" : "s");
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// Helpful answer: https://stackoverflow.com/a/35759874
|
|
18
|
+
hortis.parseFloat = function (str) {
|
|
19
|
+
return isNaN(str) ? NaN : parseFloat(str);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// https://stackoverflow.com/a/24457420
|
|
23
|
+
hortis.isInteger = function isNumeric(value) {
|
|
24
|
+
return /^-?\d+$/.test(value);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Upgrades a promise rejection payload (or Error) by suffixing an additional "while" reason into its "message" field
|
|
28
|
+
* @param {Object|Error} originError - A rejection payload. This should (at least) have the member `isError: true` set, as well as a String `message` holding a rejection reason.
|
|
29
|
+
* @param {String} whileMsg - A message describing the activity which led to this error
|
|
30
|
+
* @return {Object} The rejected payload formed by shallow cloning the supplied argument (if it is not an `Error`) and suffixing its `message` member
|
|
31
|
+
*/
|
|
32
|
+
hortis.upgradeError = function (originError, whileMsg) {
|
|
33
|
+
const error = originError instanceof Error ? originError : fluid.extend({}, originError);
|
|
34
|
+
error.message = originError.message + whileMsg;
|
|
35
|
+
return error;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
hortis.roundDecimals = function (text, places) {
|
|
39
|
+
const parsed = hortis.parseFloat(text);
|
|
40
|
+
if (!isNaN(parsed)) {
|
|
41
|
+
return +parsed.toFixed(places);
|
|
42
|
+
} else {
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
hortis.stringTemplateRegex = /\${([^\}]*)}/g;
|
|
48
|
+
|
|
49
|
+
// Template values with a ${key} syntax using a straightforward regex strategy and optional warning for missing values
|
|
50
|
+
hortis.stringTemplate = function (template, vars, warnFunc) {
|
|
51
|
+
const replacer = function (all, match) {
|
|
52
|
+
const segs = match.split(".");
|
|
53
|
+
const fetched = fluid.getImmediate(vars, segs);
|
|
54
|
+
if (warnFunc && fetched === undefined) {
|
|
55
|
+
warnFunc(match);
|
|
56
|
+
}
|
|
57
|
+
return fetched || "";
|
|
58
|
+
};
|
|
59
|
+
return template.replace(hortis.stringTemplateRegex, replacer);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Template values with a %key syntax and optional warning for missing values
|
|
63
|
+
fluid.stringTemplateWarn = function (template, values, warnFunc) {
|
|
64
|
+
let keys = Object.keys(values);
|
|
65
|
+
keys = keys.sort(fluid.compareStringLength());
|
|
66
|
+
for (let i = 0; i < keys.length; ++i) {
|
|
67
|
+
const key = keys[i];
|
|
68
|
+
const templatePlaceholder = "%" + key;
|
|
69
|
+
const replacementValue = values[key];
|
|
70
|
+
if (warnFunc && replacementValue === undefined) {
|
|
71
|
+
warnFunc(key);
|
|
72
|
+
}
|
|
73
|
+
template = template.replaceAll(templatePlaceholder, replacementValue);
|
|
74
|
+
}
|
|
75
|
+
return template;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
hortis.findDuplicates = function (array) {
|
|
79
|
+
return array.filter(function (item, index) {
|
|
80
|
+
return array.indexOf(item) !== index;
|
|
81
|
+
});
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// Taken from https://stackoverflow.com/a/64909887
|
|
85
|
+
hortis.asyncMap = async function (arr, fn) {
|
|
86
|
+
const result = [];
|
|
87
|
+
for (let idx = 0; idx < arr.length; ++idx) {
|
|
88
|
+
const cur = arr[idx];
|
|
89
|
+
result.push(await fn(cur, idx, arr));
|
|
90
|
+
}
|
|
91
|
+
return result;
|
|
92
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// This gets injected into the Leaflet app autogenerated by QGIS2web
|
|
3
|
+
|
|
4
|
+
var hortis = fluid ? fluid.registerNamespace("hortis") : {};
|
|
5
|
+
|
|
6
|
+
hortis.plethLevels = {
|
|
7
|
+
0: "#f7fcfd",
|
|
8
|
+
5: "#e5f5f9",
|
|
9
|
+
10: "#ccece6",
|
|
10
|
+
20: "#99d8c9",
|
|
11
|
+
50: "#66c2a4",
|
|
12
|
+
100: "#41ae76",
|
|
13
|
+
200: "#238b45",
|
|
14
|
+
500: "#005824"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
hortis.revKeys = Object.keys(hortis.plethLevels).reverse().map(function (stringKey) {
|
|
18
|
+
return Number(stringKey);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
hortis.getColor = function (count) {
|
|
22
|
+
for (var i = 0; i < hortis.revKeys.length; ++i) {
|
|
23
|
+
var key = hortis.revKeys[i];
|
|
24
|
+
if (count > key) {
|
|
25
|
+
return hortis.plethLevels[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
hortis.plethStyle = function (feature) {
|
|
31
|
+
return {
|
|
32
|
+
fillColor: hortis.getColor(feature.properties.speciesCount),
|
|
33
|
+
weight: 1,
|
|
34
|
+
opacity: 1,
|
|
35
|
+
color: "black",
|
|
36
|
+
//dashArray: '3',
|
|
37
|
+
fillOpacity: 0.7
|
|
38
|
+
};
|
|
39
|
+
};
|
package/src/wormify.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
var fluid = require("infusion");
|
|
6
|
+
var fs = require("fs");
|
|
7
|
+
var kettle = require("kettle");
|
|
8
|
+
var minimist = require("minimist");
|
|
9
|
+
|
|
10
|
+
require("./utils/utils.js");
|
|
11
|
+
require("./utils/settleStructure.js");
|
|
12
|
+
require("./dataProcessing/readJSON.js");
|
|
13
|
+
require("./dataProcessing/readCSV.js");
|
|
14
|
+
require("./dataProcessing/readCSVwithMap.js");
|
|
15
|
+
require("./dataProcessing/writeCSV.js");
|
|
16
|
+
require("./dataProcessing/taxonScrape.js");
|
|
17
|
+
|
|
18
|
+
require("./WoRMS/taxonAPI.js");
|
|
19
|
+
|
|
20
|
+
fluid.setLogging(true);
|
|
21
|
+
|
|
22
|
+
var hortis = fluid.registerNamespace("hortis");
|
|
23
|
+
var taxonAPIFileBase = "data/WoRMS/taxonAPI";
|
|
24
|
+
|
|
25
|
+
var source = kettle.dataSource.URL({
|
|
26
|
+
url: "https://www.marinespecies.org/rest/AphiaRecordsByName/%name?like=false&marine_only=false&offset=1",
|
|
27
|
+
termMap: {
|
|
28
|
+
name: "%name"
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
var parsedArgs = minimist(process.argv.slice(2));
|
|
33
|
+
|
|
34
|
+
var mapFile = parsedArgs.map || "data/dataPaper-I-in/dataPaper-out-map.json";
|
|
35
|
+
var map = hortis.readJSONSync(mapFile);
|
|
36
|
+
|
|
37
|
+
var file = parsedArgs._[0] || "data/dataPaper-I-in/reintegrated.csv";
|
|
38
|
+
var outFile = "reintegrated-WoRMS.csv";
|
|
39
|
+
|
|
40
|
+
var resultsPromise = hortis.csvReaderWithMap({
|
|
41
|
+
inputFile: file,
|
|
42
|
+
mapColumns: map.columns
|
|
43
|
+
}).completionPromise;
|
|
44
|
+
|
|
45
|
+
var outMapColumns = fluid.copy(map.columns);
|
|
46
|
+
|
|
47
|
+
delete outMapColumns.authority;
|
|
48
|
+
outMapColumns.authority = "Authority";
|
|
49
|
+
outMapColumns.WoRMSAuthority = "WoRMS Authority";
|
|
50
|
+
outMapColumns.mismatch = "Authority mismatch";
|
|
51
|
+
outMapColumns.accepted = "WoRMS Accepted";
|
|
52
|
+
|
|
53
|
+
resultsPromise.then(function (results) {
|
|
54
|
+
var rows = results.rows;
|
|
55
|
+
console.log("Got " + rows.length + " rows");
|
|
56
|
+
|
|
57
|
+
var queue = fluid.transform(rows, function (row) {
|
|
58
|
+
return {name: row.iNaturalistTaxonName};
|
|
59
|
+
});
|
|
60
|
+
var cachePromise = hortis.queueFetchWork(queue);
|
|
61
|
+
cachePromise.then(function (cache) {
|
|
62
|
+
var newRows = rows.map(function (oneRow) {
|
|
63
|
+
var newRow = fluid.copy(oneRow);
|
|
64
|
+
var entry = cache[oneRow.iNaturalistTaxonName];
|
|
65
|
+
newRow.WoRMSAuthority = entry ? (entry.authority ? entry.authority : entry.message) : "Internal error";
|
|
66
|
+
var authority = newRow.subtaxonAuthority || newRow.authority;
|
|
67
|
+
newRow.mismatch = newRow.WoRMSAuthority === authority ? "" : "x";
|
|
68
|
+
newRow.accepted = entry && entry.status;
|
|
69
|
+
return newRow;
|
|
70
|
+
});
|
|
71
|
+
hortis.writeCSV(outFile, outMapColumns, newRows, fluid.promise());
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// Values of "status" field in decreasing order of desirability
|
|
76
|
+
hortis.WoRMSstatus = ["accepted", "unaccepted", "deleted"];
|
|
77
|
+
|
|
78
|
+
hortis.WoRMSstatusIndex = function (status) {
|
|
79
|
+
var index = hortis.WoRMSstatus.indexOf(status);
|
|
80
|
+
return index === -1 ? hortis.WoRMSstatus.length : index;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
hortis.findBestWoRMSDoc = function (docs) {
|
|
84
|
+
var byIndex = [];
|
|
85
|
+
fluid.each(docs, function (doc) {
|
|
86
|
+
var index = hortis.WoRMSstatusIndex(doc.status);
|
|
87
|
+
fluid.pushArray(byIndex, index, doc);
|
|
88
|
+
});
|
|
89
|
+
return byIndex.find(function (slot) {
|
|
90
|
+
return slot && slot.length > 0;
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
hortis.queueFetchWork = function (queue) {
|
|
95
|
+
var that = {
|
|
96
|
+
queue: queue,
|
|
97
|
+
cache: {},
|
|
98
|
+
skipCount: 0,
|
|
99
|
+
fetched: [],
|
|
100
|
+
completion: fluid.promise()
|
|
101
|
+
};
|
|
102
|
+
var oneWork = function () {
|
|
103
|
+
if (that.queue.length) {
|
|
104
|
+
var head = that.queue.shift();
|
|
105
|
+
var name = head.name;
|
|
106
|
+
var filename = hortis.WoRMSTaxa.filenameFromTaxonName(taxonAPIFileBase, name);
|
|
107
|
+
var doc;
|
|
108
|
+
if (that.cache[name] || fs.existsSync(filename)) {
|
|
109
|
+
// hortis.noteSkip(that, "File " + filename + " exists, skipping");
|
|
110
|
+
doc = that.cache[filename];
|
|
111
|
+
if (!doc) {
|
|
112
|
+
doc = hortis.readJSONSync(filename);
|
|
113
|
+
that.cache[name] = doc;
|
|
114
|
+
}
|
|
115
|
+
// hortis.enqueueAncestry(doc, that.queue);
|
|
116
|
+
fluid.invokeLater(oneWork);
|
|
117
|
+
} else {
|
|
118
|
+
doc = source.get(head).then(function (docs) {
|
|
119
|
+
that.fetched.push(head);
|
|
120
|
+
var sortedDocs = hortis.findBestWoRMSDoc(docs);
|
|
121
|
+
if (!sortedDocs || sortedDocs.length === 0) {
|
|
122
|
+
console.log("Received no results for name " + head.name);
|
|
123
|
+
that.cache[name] = {
|
|
124
|
+
isError: true,
|
|
125
|
+
message: "Document not found"
|
|
126
|
+
};
|
|
127
|
+
} else {
|
|
128
|
+
if (sortedDocs.length > 1) {
|
|
129
|
+
console.log("Warning: received " + sortedDocs.length + " equally good results for name " + head.name);
|
|
130
|
+
}
|
|
131
|
+
// hortis.enqueueAncestry(doc, that.queue);
|
|
132
|
+
hortis.writeTaxonDoc(filename, sortedDocs[0]);
|
|
133
|
+
that.cache[name] = sortedDocs[0];
|
|
134
|
+
}
|
|
135
|
+
// that.completion.resolve(that.cache);
|
|
136
|
+
nextWork();
|
|
137
|
+
}, function (err) {
|
|
138
|
+
console.log("Received ERROR for name " + head.name, err);
|
|
139
|
+
that.cache[name] = {
|
|
140
|
+
isError: true,
|
|
141
|
+
message: err
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
that.completion.resolve(that.cache);
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var nextWork = function () {
|
|
150
|
+
setTimeout(oneWork, 1000);
|
|
151
|
+
};
|
|
152
|
+
nextWork();
|
|
153
|
+
return that.completion;
|
|
154
|
+
};
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/* eslint-env node */
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
var fluid = require("infusion");
|
|
6
|
+
var hortis = fluid.registerNamespace("hortis");
|
|
7
|
+
|
|
8
|
+
require("../dataProcessing/writeCSV.js");
|
|
9
|
+
|
|
10
|
+
fluid.registerNamespace("hortis.xetthecum");
|
|
11
|
+
|
|
12
|
+
hortis.xetthecum.names = [{
|
|
13
|
+
field: "hulqThom",
|
|
14
|
+
auth: "(Thom, 2011)"
|
|
15
|
+
}, {
|
|
16
|
+
field: "hulqTurner1",
|
|
17
|
+
auth: "(Turner, 2014)"
|
|
18
|
+
}, {
|
|
19
|
+
field: "hulqTurner2",
|
|
20
|
+
auth: "(Turner, 2014)"
|
|
21
|
+
}];
|
|
22
|
+
|
|
23
|
+
hortis.xetthecum.valueOutMap = {
|
|
24
|
+
columns: {
|
|
25
|
+
"foodValue": "Food Value",
|
|
26
|
+
"medicinalValue": "Medicinal Value",
|
|
27
|
+
"spiritualValue": "Spiritual Value",
|
|
28
|
+
"materialValue": "Material Value",
|
|
29
|
+
"tradeValue": "Trade Value",
|
|
30
|
+
"indicatorValue": "Indicator Value"
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
hortis.xetthecum.extraOutMap = {
|
|
35
|
+
columns: {
|
|
36
|
+
"hulqName": "Hulquminum Name",
|
|
37
|
+
"hulqAuth": "Hulquminum Authority"
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
hortis.xetthecum.mediaOutMap = {
|
|
42
|
+
columns: {
|
|
43
|
+
"audioLink": "Audio Link"
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
hortis.xetthecum.chooseName = function (target, hulqRow) {
|
|
48
|
+
var bestRec = hortis.xetthecum.names.find(function (rec) {
|
|
49
|
+
return !!hulqRow[rec.field];
|
|
50
|
+
});
|
|
51
|
+
if (bestRec) {
|
|
52
|
+
target.hulqName = hulqRow[bestRec.field];
|
|
53
|
+
target.hulqAuth = bestRec.auth;
|
|
54
|
+
return true;
|
|
55
|
+
} else {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
hortis.xetthecum.summariseUnseen = function (byTaxon, usedTaxa, message) {
|
|
61
|
+
var unseen = [];
|
|
62
|
+
fluid.each(byTaxon, function (value, key) {
|
|
63
|
+
if (!usedTaxa[key]) {
|
|
64
|
+
unseen.push(key);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
console.log("The following ", unseen.length, " taxa with " + message + " were not observed:\n" + unseen.join("\n"));
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
hortis.xetthecum.assignHulqNames = function (resolved, patch) {
|
|
71
|
+
var byTaxon = {};
|
|
72
|
+
var usedTaxa = {};
|
|
73
|
+
var withoutHulq = [];
|
|
74
|
+
var hits = 0;
|
|
75
|
+
var valueFields = Object.keys(hortis.xetthecum.valueOutMap.columns);
|
|
76
|
+
patch.patchData.rows.forEach(function (row) {
|
|
77
|
+
byTaxon[row.scientificName] = row;
|
|
78
|
+
});
|
|
79
|
+
resolved.summarisedRows.forEach(function (row) {
|
|
80
|
+
var taxon = row.iNaturalistTaxonName;
|
|
81
|
+
var hulqRow = byTaxon[taxon];
|
|
82
|
+
if (!hulqRow) {
|
|
83
|
+
withoutHulq.push({
|
|
84
|
+
taxon: taxon,
|
|
85
|
+
found: "no record"
|
|
86
|
+
});
|
|
87
|
+
} else {
|
|
88
|
+
usedTaxa[taxon] = true;
|
|
89
|
+
var transfer = fluid.filterKeys(hulqRow, valueFields);
|
|
90
|
+
var foundName = hortis.xetthecum.chooseName(transfer, hulqRow);
|
|
91
|
+
if (foundName) {
|
|
92
|
+
++hits;
|
|
93
|
+
} else {
|
|
94
|
+
withoutHulq.push({
|
|
95
|
+
taxon: taxon,
|
|
96
|
+
found: "no name"
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
fluid.extend(row, transfer);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
console.log("Matched " + hits + " taxa with Hul'qumi'num names");
|
|
103
|
+
|
|
104
|
+
hortis.xetthecum.summariseUnseen(byTaxon, usedTaxa, "Hul'qumi'num name records");
|
|
105
|
+
hortis.writeCSV("hulq-mismatches.csv", ["taxon", "found"], withoutHulq, fluid.promise());
|
|
106
|
+
resolved.combinedOutMap = hortis.combineMaps([resolved.combinedOutMap, hortis.xetthecum.valueOutMap, hortis.xetthecum.extraOutMap]);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
hortis.xetthecum.assignHulqMedia = function (resolved, patch) {
|
|
110
|
+
var byTaxon = {};
|
|
111
|
+
var usedTaxa = {};
|
|
112
|
+
patch.patchData.rows.forEach(function (row) {
|
|
113
|
+
var sanitized = hortis.sanitizeSpeciesName(row.scientificName);
|
|
114
|
+
byTaxon[sanitized] = row;
|
|
115
|
+
});
|
|
116
|
+
resolved.summarisedRows.forEach(function (row) {
|
|
117
|
+
var taxon = row.iNaturalistTaxonName;
|
|
118
|
+
var hulqRow = byTaxon[taxon];
|
|
119
|
+
if (hulqRow) {
|
|
120
|
+
usedTaxa[taxon] = true;
|
|
121
|
+
row.audioLink = hulqRow.audioLink;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
resolved.combinedOutMap = hortis.combineMaps([resolved.combinedOutMap, hortis.xetthecum.mediaOutMap]);
|
|
125
|
+
hortis.xetthecum.summariseUnseen(byTaxon, usedTaxa, "Hul'qumi'num media data");
|
|
126
|
+
};
|