@ca-plant-list/ca-plant-list 0.4.14 → 0.4.16
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 +24 -0
- package/data/inatobsphotos.csv +9286 -0
- package/data/inattaxonphotos.csv +27 -0
- package/data/taxa.csv +5 -5
- package/data/text/Eriastrum-ertterae.footer.md +6 -0
- package/data/text/Malacothamnus-arcuatus-var-elmeri.footer.md +3 -0
- package/data/text/Malacothamnus-fremontii-var-fremontii.footer.md +3 -0
- package/data/text/Malacothamnus-hallii.footer.md +3 -0
- package/lib/csv.js +1 -26
- package/lib/exceptions.js +2 -1
- package/lib/htmltaxon.js +14 -0
- package/lib/index.d.ts +1 -0
- package/lib/program.js +8 -4
- package/lib/taxa.js +76 -19
- package/lib/taxon.js +8 -2
- package/lib/tools/calflora.js +5 -0
- package/lib/tools/jepsoneflora.js +440 -0
- package/lib/tools/rpi.js +435 -0
- package/lib/utils/inat-tools.js +4 -4
- package/lib/web/pagetaxon.js +2 -8
- package/package.json +22 -6
- package/scripts/cpl-photos.js +1 -0
- package/scripts/cpl-tools.js +21 -47
- package/scripts/dev/publish.sh +3 -0
- package/scripts/inatobsphotos.js +113 -0
- package/scripts/inattaxonphotos.js +10 -4
- package/types/classes.d.ts +33 -8
- package/.github/workflows/main.yml +0 -20
- package/.prettierrc +0 -3
- package/.vscode/settings.json +0 -9
- package/eslint.config.mjs +0 -13
- package/schemas/exceptions.schema.json +0 -62
- package/tmp/config.json +0 -21
- package/tmp/exceptions.json +0 -93
- package/tmp/families.json +0 -790
- package/tmp/genera.json +0 -5566
- package/tmp/inattaxonphotos.csv +0 -6059
- package/tmp/synonyms.csv +0 -2141
- package/tmp/taxa_include.csv +0 -19
package/lib/tools/rpi.js
ADDED
@@ -0,0 +1,435 @@
|
|
1
|
+
import path from "node:path";
|
2
|
+
import { CSV } from "../csv.js";
|
3
|
+
import { Files } from "../files.js";
|
4
|
+
|
5
|
+
const HTML_FILE_NAME = "rpi.html";
|
6
|
+
const URL_RPI_LIST =
|
7
|
+
"https://rareplants.cnps.org/Search/result?frm=T&life=tree:herb:shrub:vine:leaf:stem";
|
8
|
+
|
9
|
+
class RPI {
|
10
|
+
/** @type {Object<string,Object<string,string>>} */
|
11
|
+
static #rpiData = {};
|
12
|
+
|
13
|
+
/**
|
14
|
+
* @param {string} toolsDataDir
|
15
|
+
* @param {Taxa} taxa
|
16
|
+
* @param {Config} config
|
17
|
+
* @param {import("../exceptions.js").Exceptions} exceptions
|
18
|
+
* @param {ErrorLog} errorLog
|
19
|
+
*/
|
20
|
+
static async analyze(toolsDataDir, taxa, config, exceptions, errorLog) {
|
21
|
+
/**
|
22
|
+
* @param {string} name
|
23
|
+
* @param {string} label
|
24
|
+
* @param {string|undefined} rpiRank
|
25
|
+
* @param {string|undefined} taxonRank
|
26
|
+
*/
|
27
|
+
function checkStatusMatch(name, label, rpiRank, taxonRank) {
|
28
|
+
if (rpiRank !== taxonRank) {
|
29
|
+
errorLog.log(
|
30
|
+
name,
|
31
|
+
label +
|
32
|
+
" rank in taxa.csv is different than rank in " +
|
33
|
+
fileName,
|
34
|
+
String(taxonRank),
|
35
|
+
String(rpiRank),
|
36
|
+
);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
const toolsDataPath = toolsDataDir + "/rpi";
|
41
|
+
const fileName = "rpi.csv";
|
42
|
+
const filePath = toolsDataPath + "/" + fileName;
|
43
|
+
|
44
|
+
// Create data directory if it's not there.
|
45
|
+
Files.mkdir(toolsDataPath);
|
46
|
+
|
47
|
+
// Download the data file if it doesn't exist.
|
48
|
+
if (!Files.exists(filePath)) {
|
49
|
+
console.log("retrieving " + filePath);
|
50
|
+
|
51
|
+
// To download results in a spreadsheet, first need to retrieve the search results HTML, which sets the
|
52
|
+
// ASP.NET_SessionId session cookie, then retrieve the CSV, which retrieves the query from the session.
|
53
|
+
const headers = await Files.fetch(
|
54
|
+
URL_RPI_LIST,
|
55
|
+
toolsDataPath + "/" + HTML_FILE_NAME,
|
56
|
+
);
|
57
|
+
|
58
|
+
const options = { headers: { cookie: headers.get("set-cookie") } };
|
59
|
+
await Files.fetch(
|
60
|
+
"https://rareplants.cnps.org/PlantExport/SearchResults",
|
61
|
+
filePath,
|
62
|
+
options,
|
63
|
+
);
|
64
|
+
}
|
65
|
+
|
66
|
+
const countyCodes = config.getCountyCodes();
|
67
|
+
const ignoreGlobalRank = config.getConfigValue("rpi", "ignoreglobal");
|
68
|
+
const ignoreCNDDBRank = config.getConfigValue("rpi", "ignorecnddb");
|
69
|
+
|
70
|
+
const csv = CSV.readFile(path.join(toolsDataPath, fileName));
|
71
|
+
for (const row of csv) {
|
72
|
+
const rpiName = row["ScientificName"].replace(" ssp.", " subsp.");
|
73
|
+
const translatedName = exceptions.getValue(
|
74
|
+
rpiName,
|
75
|
+
"rpi",
|
76
|
+
"translation",
|
77
|
+
);
|
78
|
+
this.#rpiData[rpiName] = row;
|
79
|
+
const name = translatedName ? translatedName : rpiName;
|
80
|
+
const rank = row["CRPR"];
|
81
|
+
const rawCESA = row["CESA"];
|
82
|
+
const rawFESA = row["FESA"];
|
83
|
+
const cesa = rawCESA === "None" ? undefined : rawCESA;
|
84
|
+
const fesa = rawFESA === "None" ? undefined : rawFESA;
|
85
|
+
const cnddb = row["SRank"];
|
86
|
+
const globalRank = row["GRank"];
|
87
|
+
const taxon = taxa.getTaxon(name);
|
88
|
+
|
89
|
+
const shouldBeInGeo = this.#shouldBeHere(row, countyCodes);
|
90
|
+
|
91
|
+
if (shouldBeInGeo) {
|
92
|
+
if (!taxon) {
|
93
|
+
if (cesa && countyCodes.length > 0) {
|
94
|
+
errorLog.log(
|
95
|
+
name,
|
96
|
+
"is CESA listed but not found in taxa.csv",
|
97
|
+
cesa,
|
98
|
+
);
|
99
|
+
}
|
100
|
+
if (this.#hasExceptions(name, exceptions, "notingeo")) {
|
101
|
+
continue;
|
102
|
+
}
|
103
|
+
if (
|
104
|
+
this.#hasExceptions(name, exceptions, "extirpated") &&
|
105
|
+
(rank === "1A" || rank === "2A")
|
106
|
+
) {
|
107
|
+
// It is state ranked, but extirpated statewide, so we are not tracking it.
|
108
|
+
continue;
|
109
|
+
}
|
110
|
+
if (countyCodes.length > 0) {
|
111
|
+
errorLog.log(
|
112
|
+
name,
|
113
|
+
"in RPI but not found in taxa.csv",
|
114
|
+
rank,
|
115
|
+
);
|
116
|
+
}
|
117
|
+
continue;
|
118
|
+
}
|
119
|
+
} else {
|
120
|
+
if (!taxon) {
|
121
|
+
// Not in taxa.csv, and also not in RPI for local counties, so ignore it.
|
122
|
+
continue;
|
123
|
+
}
|
124
|
+
if (
|
125
|
+
taxon.isNative() &&
|
126
|
+
!this.#hasExceptions(name, exceptions, "ingeo")
|
127
|
+
) {
|
128
|
+
// If it is a local native in taxa.csv, warn.
|
129
|
+
errorLog.log(
|
130
|
+
name,
|
131
|
+
"in taxa.csv but not in RPI for local counties",
|
132
|
+
rank,
|
133
|
+
);
|
134
|
+
}
|
135
|
+
}
|
136
|
+
|
137
|
+
if (rank !== taxon.getRPIRankAndThreat()) {
|
138
|
+
if (taxon.isNative()) {
|
139
|
+
errorLog.log(
|
140
|
+
name,
|
141
|
+
"rank in taxa.csv is different than rank in " +
|
142
|
+
fileName,
|
143
|
+
taxon.getRPIRankAndThreat(),
|
144
|
+
rank,
|
145
|
+
);
|
146
|
+
}
|
147
|
+
}
|
148
|
+
checkStatusMatch(name, "CESA", cesa, taxon.getCESA());
|
149
|
+
checkStatusMatch(name, "FESA", fesa, taxon.getFESA());
|
150
|
+
if (!ignoreCNDDBRank) {
|
151
|
+
checkStatusMatch(name, "CNDDB", cnddb, taxon.getCNDDBRank());
|
152
|
+
}
|
153
|
+
if (!ignoreGlobalRank) {
|
154
|
+
checkStatusMatch(
|
155
|
+
name,
|
156
|
+
"Global",
|
157
|
+
globalRank,
|
158
|
+
taxon.getGlobalRank(),
|
159
|
+
);
|
160
|
+
}
|
161
|
+
|
162
|
+
if (
|
163
|
+
!taxon.isCANative() &&
|
164
|
+
!this.#hasExceptions(name, exceptions, "non-native")
|
165
|
+
) {
|
166
|
+
errorLog.log(name, "is in RPI but not native in taxa.csv");
|
167
|
+
}
|
168
|
+
}
|
169
|
+
|
170
|
+
// Check all taxa to make sure they are consistent with RPI.
|
171
|
+
for (const taxon of taxa.getTaxonList()) {
|
172
|
+
const name = taxon.getName();
|
173
|
+
if (taxon.getRPIRankAndThreat()) {
|
174
|
+
const translatedName = exceptions.getValue(
|
175
|
+
name,
|
176
|
+
"rpi",
|
177
|
+
"translation-to-rpi",
|
178
|
+
);
|
179
|
+
// Make sure it is in RPI data.
|
180
|
+
if (!this.#rpiData[translatedName ? translatedName : name]) {
|
181
|
+
errorLog.log(
|
182
|
+
name,
|
183
|
+
"has CRPR in taxa.csv but is not in " + fileName,
|
184
|
+
);
|
185
|
+
}
|
186
|
+
} else {
|
187
|
+
// If it is not in RPI, it shouldn't have any of the other ranks.
|
188
|
+
if (
|
189
|
+
taxon.getCESA() ||
|
190
|
+
taxon.getFESA() ||
|
191
|
+
taxon.getCNDDBRank() ||
|
192
|
+
taxon.getGlobalRank()
|
193
|
+
) {
|
194
|
+
errorLog.log(name, "has no CRPR but has other ranks");
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
198
|
+
|
199
|
+
this.#checkExceptions(taxa, config, exceptions, errorLog);
|
200
|
+
|
201
|
+
this.#scrape(toolsDataDir, taxa, exceptions, errorLog);
|
202
|
+
}
|
203
|
+
|
204
|
+
/**
|
205
|
+
*
|
206
|
+
* @param {Taxa} taxa
|
207
|
+
* @param {Config} config
|
208
|
+
* @param {import("../exceptions.js").Exceptions} exceptions
|
209
|
+
* @param {ErrorLog} errorLog
|
210
|
+
*/
|
211
|
+
static #checkExceptions(taxa, config, exceptions, errorLog) {
|
212
|
+
const countyCodes = config.getCountyCodes();
|
213
|
+
|
214
|
+
// Check the RPI exceptions and make sure they still apply.
|
215
|
+
for (const [name, v] of exceptions.getExceptions()) {
|
216
|
+
const rpiExceptions = v.rpi;
|
217
|
+
if (!rpiExceptions) {
|
218
|
+
continue;
|
219
|
+
}
|
220
|
+
|
221
|
+
const translatedName = exceptions.getValue(
|
222
|
+
name,
|
223
|
+
"rpi",
|
224
|
+
"translation",
|
225
|
+
);
|
226
|
+
|
227
|
+
const taxon = taxa.getTaxon(translatedName ? translatedName : name);
|
228
|
+
|
229
|
+
// Make sure it is actually in RPI data.
|
230
|
+
const rpiData = this.#rpiData[name];
|
231
|
+
if (!rpiData) {
|
232
|
+
// Ignore it if there is a "translation-to-rpi" entry.
|
233
|
+
if (!rpiExceptions["translation-to-rpi"]) {
|
234
|
+
errorLog.log(
|
235
|
+
name,
|
236
|
+
"has rpi exception but is not in rpi.csv",
|
237
|
+
);
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
for (const [k, v] of Object.entries(rpiExceptions)) {
|
242
|
+
switch (k) {
|
243
|
+
case "extirpated": {
|
244
|
+
// Make sure the taxon is not in our list.
|
245
|
+
if (taxon) {
|
246
|
+
errorLog.log(
|
247
|
+
name,
|
248
|
+
"has rpi extirpated exception but is in taxa.csv",
|
249
|
+
);
|
250
|
+
}
|
251
|
+
// Make sure it has extirpated RPI status.
|
252
|
+
const rank = rpiData["CRPR"];
|
253
|
+
if (rank !== "1A" && rank !== "2A") {
|
254
|
+
errorLog.log(
|
255
|
+
name,
|
256
|
+
"has rpi extirpated exception rank is not 1A or 2A",
|
257
|
+
);
|
258
|
+
}
|
259
|
+
break;
|
260
|
+
}
|
261
|
+
case "ingeo":
|
262
|
+
// Make sure the taxon is in our list.
|
263
|
+
if (!taxon) {
|
264
|
+
errorLog.log(
|
265
|
+
name,
|
266
|
+
"has rpi ingeo exception but is not in taxa.csv",
|
267
|
+
);
|
268
|
+
}
|
269
|
+
// Make sure it is no listed in local area in RPI.
|
270
|
+
if (this.#shouldBeHere(rpiData, countyCodes)) {
|
271
|
+
errorLog.log(
|
272
|
+
name,
|
273
|
+
"has rpi ingeo exception but is listed in local counties in rpi.csv",
|
274
|
+
);
|
275
|
+
}
|
276
|
+
break;
|
277
|
+
case "non-native":
|
278
|
+
// Make sure the taxon is in our list.
|
279
|
+
if (!taxon) {
|
280
|
+
errorLog.log(
|
281
|
+
name,
|
282
|
+
"has rpi non-native exception but is not in taxa.csv",
|
283
|
+
);
|
284
|
+
continue;
|
285
|
+
}
|
286
|
+
// Make sure it is non-native in our list.
|
287
|
+
if (taxon.isCANative()) {
|
288
|
+
errorLog.log(
|
289
|
+
name,
|
290
|
+
"has rpi non-native exception but is native in local list",
|
291
|
+
);
|
292
|
+
}
|
293
|
+
break;
|
294
|
+
case "notingeo":
|
295
|
+
// Make sure the taxon is not in our list.
|
296
|
+
if (taxon) {
|
297
|
+
errorLog.log(
|
298
|
+
name,
|
299
|
+
"has rpi notingeo exception but is in taxa.csv",
|
300
|
+
);
|
301
|
+
}
|
302
|
+
// Make sure it is listed in local area in RPI.
|
303
|
+
if (!this.#shouldBeHere(rpiData, countyCodes)) {
|
304
|
+
errorLog.log(
|
305
|
+
name,
|
306
|
+
"has rpi notingeo exception but is not listed in local counties in rpi.csv",
|
307
|
+
);
|
308
|
+
}
|
309
|
+
break;
|
310
|
+
case "translation": {
|
311
|
+
// Make sure the translated name is in our list.
|
312
|
+
if (!taxon) {
|
313
|
+
errorLog.log(
|
314
|
+
name,
|
315
|
+
"has rpi translation exception, but target not found",
|
316
|
+
);
|
317
|
+
}
|
318
|
+
// Make sure there is a matching translation exception.
|
319
|
+
const translatedName = exceptions.getValue(
|
320
|
+
v,
|
321
|
+
"rpi",
|
322
|
+
"translation-to-rpi",
|
323
|
+
);
|
324
|
+
if (translatedName !== name) {
|
325
|
+
errorLog.log(
|
326
|
+
name,
|
327
|
+
"has rpi translation exception, but no reverse translation",
|
328
|
+
);
|
329
|
+
}
|
330
|
+
break;
|
331
|
+
}
|
332
|
+
case "translation-to-rpi": {
|
333
|
+
// Make sure there is a matching translation exception.
|
334
|
+
const translatedName = exceptions.getValue(
|
335
|
+
v,
|
336
|
+
"rpi",
|
337
|
+
"translation",
|
338
|
+
);
|
339
|
+
if (translatedName !== name) {
|
340
|
+
errorLog.log(
|
341
|
+
name,
|
342
|
+
"has rpi translation-to-rpi exception, but no reverse translation",
|
343
|
+
);
|
344
|
+
}
|
345
|
+
break;
|
346
|
+
}
|
347
|
+
default:
|
348
|
+
errorLog.log(name, "unrecognized rpi exception", k);
|
349
|
+
}
|
350
|
+
}
|
351
|
+
}
|
352
|
+
}
|
353
|
+
|
354
|
+
/**
|
355
|
+
* @param {string} name
|
356
|
+
* @param {import("../exceptions.js").Exceptions} exceptions
|
357
|
+
* @param {...string} args
|
358
|
+
*/
|
359
|
+
static #hasExceptions(name, exceptions, ...args) {
|
360
|
+
for (const arg of args) {
|
361
|
+
if (exceptions.hasException(name, "rpi", arg)) {
|
362
|
+
return true;
|
363
|
+
}
|
364
|
+
}
|
365
|
+
return false;
|
366
|
+
}
|
367
|
+
|
368
|
+
/**
|
369
|
+
* @param {string} toolsDataDir
|
370
|
+
* @param {Taxa} taxa
|
371
|
+
* @param {import("../exceptions.js").Exceptions} exceptions
|
372
|
+
* @param {ErrorLog} errorLog
|
373
|
+
*/
|
374
|
+
static async #scrape(toolsDataDir, taxa, exceptions, errorLog) {
|
375
|
+
const toolsDataPath = toolsDataDir + "/rpi";
|
376
|
+
const fileName = HTML_FILE_NAME;
|
377
|
+
const filePath = toolsDataPath + "/" + fileName;
|
378
|
+
|
379
|
+
const html = Files.read(filePath);
|
380
|
+
const re = /href="\/Plants\/Details\/(\d+)".*?>(.*?)<\/a>/gs;
|
381
|
+
const matches = [...html.matchAll(re)];
|
382
|
+
/** @type {Object<string,string>} */
|
383
|
+
const rpiIDs = {};
|
384
|
+
for (const match of matches) {
|
385
|
+
const id = match[1];
|
386
|
+
const name = match[2]
|
387
|
+
.replaceAll(/<\/?em>/g, "")
|
388
|
+
.trim()
|
389
|
+
.replace(" ssp.", " subsp.");
|
390
|
+
rpiIDs[name] = id;
|
391
|
+
}
|
392
|
+
|
393
|
+
for (const taxon of taxa.getTaxonList()) {
|
394
|
+
if (!taxon.getRPIRankAndThreat()) {
|
395
|
+
continue;
|
396
|
+
}
|
397
|
+
const name = taxon.getName();
|
398
|
+
const translatedName =
|
399
|
+
exceptions.getValue(name, "rpi", "translation-to-rpi", name) ??
|
400
|
+
name;
|
401
|
+
const id = rpiIDs[translatedName];
|
402
|
+
if (!id) {
|
403
|
+
errorLog.log(name, "not found in RPI HTML", translatedName);
|
404
|
+
}
|
405
|
+
if (id !== taxon.getRPIID()) {
|
406
|
+
errorLog.log(
|
407
|
+
name,
|
408
|
+
"RPI ID in " + fileName + " does not match site data",
|
409
|
+
id,
|
410
|
+
taxon.getRPIID(),
|
411
|
+
);
|
412
|
+
}
|
413
|
+
}
|
414
|
+
}
|
415
|
+
|
416
|
+
/**
|
417
|
+
* @param {Object<string,string>} row
|
418
|
+
* @param {string[]} countyCodes
|
419
|
+
*/
|
420
|
+
static #shouldBeHere(row, countyCodes) {
|
421
|
+
if (countyCodes.length === 0) {
|
422
|
+
return true;
|
423
|
+
}
|
424
|
+
|
425
|
+
const rpiCounties = row["Counties"];
|
426
|
+
for (const countyCode of countyCodes) {
|
427
|
+
if (rpiCounties.includes(countyCode)) {
|
428
|
+
return true;
|
429
|
+
}
|
430
|
+
}
|
431
|
+
return false;
|
432
|
+
}
|
433
|
+
}
|
434
|
+
|
435
|
+
export { RPI };
|
package/lib/utils/inat-tools.js
CHANGED
@@ -55,10 +55,10 @@ export async function getTaxonPhotos(taxaToUpdate) {
|
|
55
55
|
/** @type {InatPhotoInfo[]} */
|
56
56
|
const taxonPhotos = [];
|
57
57
|
for (const taxonPhoto of iNatTaxonPhotos) {
|
58
|
-
const
|
59
|
-
if (!
|
60
|
-
|
61
|
-
|
58
|
+
const url = taxonPhoto.photo.medium_url || taxonPhoto.photo.url;
|
59
|
+
if (!url) continue;
|
60
|
+
const ext = url.split(".").at(-1);
|
61
|
+
if (!ext) continue;
|
62
62
|
/** @type {InatPhotoInfo} */
|
63
63
|
const obj = {
|
64
64
|
id: taxonPhoto.photo.id.toString(),
|
package/lib/web/pagetaxon.js
CHANGED
@@ -4,7 +4,6 @@ import { GenericPage } from "../genericpage.js";
|
|
4
4
|
import { ExternalSites } from "../externalsites.js";
|
5
5
|
import { HTML } from "../html.js";
|
6
6
|
import { HTMLTaxon } from "../htmltaxon.js";
|
7
|
-
import { Config } from "../config.js";
|
8
7
|
|
9
8
|
class PageTaxon extends GenericPage {
|
10
9
|
#config;
|
@@ -188,14 +187,9 @@ class PageTaxon extends GenericPage {
|
|
188
187
|
);
|
189
188
|
html += "</div>";
|
190
189
|
|
191
|
-
|
192
|
-
Config.getPackageDir() +
|
193
|
-
"/data/text/" +
|
194
|
-
this.getBaseFileName() +
|
195
|
-
".footer.md";
|
196
|
-
html += HTMLTaxon.getMarkdownSection(footerTextPath);
|
190
|
+
html += HTMLTaxon.getFooterHTML(this.#taxon);
|
197
191
|
|
198
|
-
const photos = this.#taxon.getPhotos();
|
192
|
+
const photos = this.#taxon.getPhotos().slice( 0, 5 );
|
199
193
|
if (photos.length > 0) {
|
200
194
|
let photosHtml = "";
|
201
195
|
for (const photo of photos) {
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@ca-plant-list/ca-plant-list",
|
3
|
-
"version": "0.4.
|
3
|
+
"version": "0.4.16",
|
4
4
|
"description": "Tools to create Jekyll files for a website listing plants in an area of California.",
|
5
5
|
"license": "MIT",
|
6
6
|
"repository": {
|
@@ -9,15 +9,30 @@
|
|
9
9
|
},
|
10
10
|
"homepage": "https://github.com/ca-plants/ca-plant-list",
|
11
11
|
"type": "module",
|
12
|
+
"files": [
|
13
|
+
"data",
|
14
|
+
"ebook",
|
15
|
+
"jekyll",
|
16
|
+
"lib",
|
17
|
+
"scripts",
|
18
|
+
"types"
|
19
|
+
],
|
12
20
|
"exports": {
|
13
21
|
".": "./lib/index.js"
|
14
22
|
},
|
15
23
|
"types": "./lib/index.d.ts",
|
24
|
+
"scripts": {
|
25
|
+
"check": "npm run eslint && npm run tsc",
|
26
|
+
"eslint": "npx eslint",
|
27
|
+
"prettier": "npx prettier -l .",
|
28
|
+
"tsc": "npx tsc"
|
29
|
+
},
|
16
30
|
"bin": {
|
17
31
|
"ca-plant-list": "scripts/build-site.js",
|
18
32
|
"ca-plant-book": "scripts/build-ebook.js",
|
19
33
|
"cpl-photos": "scripts/cpl-photos.js",
|
20
|
-
"cpl-tools": "scripts/cpl-tools.js"
|
34
|
+
"cpl-tools": "scripts/cpl-tools.js",
|
35
|
+
"inatobsphotos": "scripts/inatobsphotos.js"
|
21
36
|
},
|
22
37
|
"dependencies": {
|
23
38
|
"archiver": "^5.3.1",
|
@@ -32,14 +47,15 @@
|
|
32
47
|
"unzipper": "^0.10.11"
|
33
48
|
},
|
34
49
|
"devDependencies": {
|
50
|
+
"@htmltools/scrape": "^0.1.0",
|
35
51
|
"@types/archiver": "^6.0.2",
|
36
52
|
"@types/cli-progress": "^3.11.6",
|
37
53
|
"@types/markdown-it": "^14.1.2",
|
38
|
-
"@types/node": "^22.
|
54
|
+
"@types/node": "^22.10.2",
|
39
55
|
"@types/unzipper": "^0.10.9",
|
40
|
-
"eslint": "^9.
|
56
|
+
"eslint": "^9.17.0",
|
41
57
|
"exceljs": "^4.4.0",
|
42
|
-
"prettier": "^3.
|
43
|
-
"typescript": "^5.
|
58
|
+
"prettier": "^3.4.2",
|
59
|
+
"typescript": "^5.7.2"
|
44
60
|
}
|
45
61
|
}
|
package/scripts/cpl-photos.js
CHANGED
package/scripts/cpl-tools.js
CHANGED
@@ -1,14 +1,16 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
2
|
|
3
|
-
import * as path from "node:path";
|
4
3
|
import { Option } from "commander";
|
5
|
-
import { Taxa } from "../lib/taxa.js";
|
6
4
|
import { Program } from "../lib/program.js";
|
7
5
|
import { Calflora } from "../lib/tools/calflora.js";
|
8
6
|
import { Exceptions } from "../lib/exceptions.js";
|
9
7
|
import { ErrorLog } from "../lib/errorlog.js";
|
10
8
|
import { Calscape } from "../lib/tools/calscape.js";
|
11
9
|
import { INat } from "../lib/tools/inat.js";
|
10
|
+
import { JepsonEFlora } from "../lib/tools/jepsoneflora.js";
|
11
|
+
import { RPI } from "../lib/tools/rpi.js";
|
12
|
+
import { Config } from "../lib/config.js";
|
13
|
+
import { Taxa } from "../lib/taxa.js";
|
12
14
|
|
13
15
|
const TOOLS = {
|
14
16
|
CALFLORA: "calflora",
|
@@ -29,7 +31,6 @@ const ALL_TOOLS = [
|
|
29
31
|
TOOLS.TEXT,
|
30
32
|
];
|
31
33
|
|
32
|
-
const OPT_LOADER = "loader";
|
33
34
|
const OPT_TOOL = "tool";
|
34
35
|
|
35
36
|
const TOOLS_DATA_DIR = "./external_data";
|
@@ -48,8 +49,8 @@ async function build(program, options) {
|
|
48
49
|
}
|
49
50
|
|
50
51
|
const exceptions = new Exceptions(options.datadir);
|
51
|
-
|
52
|
-
const taxa = await
|
52
|
+
const config = new Config(options.datadir);
|
53
|
+
const taxa = await Taxa.loadTaxa(options);
|
53
54
|
|
54
55
|
const errorLog = new ErrorLog(options.outputdir + "/log.tsv", true);
|
55
56
|
for (const tool of tools) {
|
@@ -84,26 +85,26 @@ async function build(program, options) {
|
|
84
85
|
);
|
85
86
|
break;
|
86
87
|
case TOOLS.JEPSON_EFLORA: {
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
88
|
+
const eflora = new JepsonEFlora(
|
89
|
+
TOOLS_DATA_DIR,
|
90
|
+
taxa,
|
91
|
+
errorLog,
|
92
|
+
options.efLognotes,
|
93
|
+
);
|
94
|
+
await eflora.analyze(exceptions);
|
94
95
|
break;
|
95
96
|
}
|
96
97
|
case TOOLS.JEPSON_FAM:
|
97
98
|
// await JepsonFamilies.build(TOOLS_DATA_DIR, options.outputdir);
|
98
99
|
break;
|
99
100
|
case TOOLS.RPI:
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
101
|
+
await RPI.analyze(
|
102
|
+
TOOLS_DATA_DIR,
|
103
|
+
taxa,
|
104
|
+
config,
|
105
|
+
exceptions,
|
106
|
+
errorLog,
|
107
|
+
);
|
107
108
|
break;
|
108
109
|
case TOOLS.TEXT:
|
109
110
|
// SupplementalText.analyze(taxa, errorLog);
|
@@ -117,29 +118,6 @@ async function build(program, options) {
|
|
117
118
|
errorLog.write();
|
118
119
|
}
|
119
120
|
|
120
|
-
/**
|
121
|
-
* @param {import("commander").OptionValues} options
|
122
|
-
*/
|
123
|
-
async function getTaxa(options) {
|
124
|
-
const errorLog = new ErrorLog(options.outputdir + "/errors.tsv", true);
|
125
|
-
|
126
|
-
const loader = options[OPT_LOADER];
|
127
|
-
let taxa;
|
128
|
-
if (loader) {
|
129
|
-
const taxaLoaderClass = await import("file:" + path.resolve(loader));
|
130
|
-
taxa = await taxaLoaderClass.TaxaLoader.loadTaxa(options, errorLog);
|
131
|
-
} else {
|
132
|
-
taxa = new Taxa(
|
133
|
-
Program.getIncludeList(options.datadir),
|
134
|
-
errorLog,
|
135
|
-
options.showFlowerErrors,
|
136
|
-
);
|
137
|
-
}
|
138
|
-
|
139
|
-
errorLog.write();
|
140
|
-
return taxa;
|
141
|
-
}
|
142
|
-
|
143
121
|
const program = Program.getProgram();
|
144
122
|
program.addOption(
|
145
123
|
new Option(
|
@@ -156,16 +134,12 @@ program.option(
|
|
156
134
|
"--ef-lognotes",
|
157
135
|
"When running the jepson-eflora tool, include eFlora notes, invalid names, etc. in the log file.",
|
158
136
|
);
|
159
|
-
program.option(
|
160
|
-
"--loader <path>",
|
161
|
-
"The path (relative to the current directory) of the JavaScript file containing the TaxaLoader class. If not provided, the default TaxaLoader will be used.",
|
162
|
-
);
|
163
137
|
program.option("--update", "Update taxa.csv to remove errors if possible.");
|
164
138
|
program.addHelpText(
|
165
139
|
"after",
|
166
140
|
`
|
167
141
|
Tools:
|
168
|
-
'all' runs the 'calflora', 'inat', 'jepson-eflora', 'rpi', and 'text' tools.
|
142
|
+
'all' runs the 'calflora', '${TOOLS.CALSCAPE}', 'inat', 'jepson-eflora', 'rpi', and 'text' tools.
|
169
143
|
'${TOOLS.CALFLORA}' retrieves data from Calflora and compares with local data.
|
170
144
|
'${TOOLS.CALSCAPE}' retrieves data from Calscape and compares with local data.
|
171
145
|
'${TOOLS.INAT}' retrieves data from iNaturalist and compares with local data.
|