@ca-plant-list/ca-plant-list 0.4.18 → 0.4.20
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/data/genera.json +36 -32
- package/data/inattaxonphotos.csv +4 -0
- package/data/synonyms.csv +2 -0
- package/data/taxa.csv +6 -5
- package/data/text/Polypodium-calirhiza.md +1 -0
- package/data/text/Polypodium-scouleri.md +1 -0
- package/data/text/Rumex-conglomeratus.md +1 -0
- package/data/text/Rumex-obtusifolius.md +1 -0
- package/data/text/Rumex-pulcher.md +1 -0
- package/data/text/Rumex-salicifolius.md +1 -0
- package/lib/basepagerenderer.js +3 -3
- package/lib/ebook/glossarypages.js +3 -3
- package/lib/ebook/images.js +7 -7
- package/lib/ebook/pages/page_list_families.js +1 -1
- package/lib/ebook/pages/page_list_flower_color.js +2 -2
- package/lib/ebook/pages/page_list_flowers.js +8 -8
- package/lib/ebook/pages/page_list_species.js +1 -1
- package/lib/ebook/pages/taxonpage.js +2 -2
- package/lib/ebook/pages/tocpage.js +2 -2
- package/lib/ebook/plantbook.js +3 -3
- package/lib/externalsites.js +20 -15
- package/lib/families.js +14 -14
- package/lib/flowercolor.js +2 -2
- package/lib/genera.js +3 -3
- package/lib/htmltaxon.js +16 -7
- package/lib/index.d.ts +46 -1
- package/lib/pagerenderer.js +223 -220
- package/lib/photo.js +52 -31
- package/lib/plants/glossary.js +2 -4
- package/lib/program.js +10 -2
- package/lib/sitegenerator.js +13 -3
- package/lib/taxa.js +16 -12
- package/lib/taxon.js +12 -6
- package/lib/tools/calflora.js +41 -8
- package/lib/tools/calscape.js +4 -4
- package/lib/tools/inat.js +7 -7
- package/lib/tools/jepsoneflora.js +28 -4
- package/lib/tools/jepsonfamilies.js +102 -0
- package/lib/tools/rpi.js +8 -9
- package/lib/tools/supplementaltext.js +43 -0
- package/lib/tools/taxacsv.js +2 -2
- package/lib/utils/inat-tools.js +39 -2
- package/lib/web/glossarypages.js +6 -6
- package/lib/web/pagetaxon.js +4 -6
- package/package.json +10 -8
- package/scripts/cpl-photos.js +2 -2
- package/scripts/cpl-tools.js +11 -3
- package/scripts/inatobsphotos.js +10 -1
- package/scripts/inattaxonphotos.js +45 -43
- package/lib/inat_photo.js +0 -43
- package/types/classes.d.ts +0 -232
package/lib/photo.js
CHANGED
@@ -1,44 +1,65 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
const COPYRIGHT = "C";
|
1
|
+
/**
|
2
|
+
* @typedef {"CC0" | "CC BY" | "CC BY-NC" | "C" | null} PhotoRights
|
3
|
+
*/
|
5
4
|
|
6
|
-
class Photo {
|
7
|
-
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
rights;
|
5
|
+
export class Photo {
|
6
|
+
#id;
|
7
|
+
#ext;
|
8
|
+
#rightsHolder;
|
9
|
+
/** @type {PhotoRights} */
|
10
|
+
#rights;
|
13
11
|
|
14
12
|
/**
|
15
|
-
* @param {
|
16
|
-
* @param {string
|
17
|
-
* @param {
|
13
|
+
* @param {number} id
|
14
|
+
* @param {string} ext
|
15
|
+
* @param {import("./utils/inat-tools.js").InatLicenseCode} licenseCode
|
16
|
+
* @param {string} rightsHolder
|
18
17
|
*/
|
19
|
-
constructor(
|
20
|
-
this.#
|
21
|
-
this
|
22
|
-
this
|
18
|
+
constructor(id, ext, licenseCode, rightsHolder) {
|
19
|
+
this.#id = id;
|
20
|
+
this.#ext = ext;
|
21
|
+
this.#rightsHolder = rightsHolder;
|
22
|
+
if (licenseCode === "cc0") this.#rights = "CC0";
|
23
|
+
else if (licenseCode === "cc-by") this.#rights = "CC BY";
|
24
|
+
else if (licenseCode === "cc-by-nc") this.#rights = "CC BY-NC";
|
25
|
+
else this.#rights = "C";
|
23
26
|
}
|
24
27
|
|
25
|
-
|
26
|
-
|
28
|
+
/**
|
29
|
+
* @returns {string}
|
30
|
+
*/
|
31
|
+
getAttribution() {
|
32
|
+
if (this.#rights === "CC0") {
|
33
|
+
if (this.#rightsHolder) {
|
34
|
+
return `By ${this.#rightsHolder} (${this.#rights})`;
|
35
|
+
}
|
36
|
+
return this.#rights;
|
37
|
+
}
|
38
|
+
if (this.#rightsHolder) {
|
39
|
+
return `(c) ${this.#rightsHolder} (${this.#rights})`;
|
40
|
+
}
|
41
|
+
return `(c) (${this.#rights})`;
|
42
|
+
}
|
43
|
+
|
44
|
+
getExt() {
|
45
|
+
return this.#ext;
|
46
|
+
}
|
47
|
+
|
48
|
+
getId() {
|
49
|
+
return this.#id;
|
27
50
|
}
|
28
51
|
|
29
52
|
/**
|
30
|
-
*
|
31
|
-
* @return {string?}
|
53
|
+
* @returns {string} The URL of the iNaturalist page with details about the image.
|
32
54
|
*/
|
33
55
|
getSourceUrl() {
|
34
|
-
return
|
56
|
+
return `https://www.inaturalist.org/photos/${this.#id}`;
|
35
57
|
}
|
36
|
-
}
|
37
58
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
}
|
59
|
+
/**
|
60
|
+
* @returns {string} The URL to retrieve the image file.
|
61
|
+
*/
|
62
|
+
getUrl() {
|
63
|
+
return `https://inaturalist-open-data.s3.amazonaws.com/photos/${this.#id}/medium.${this.#ext}`;
|
64
|
+
}
|
65
|
+
}
|
package/lib/plants/glossary.js
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
import { Config } from "../config.js";
|
2
2
|
import { Files } from "../files.js";
|
3
3
|
|
4
|
-
class Glossary {
|
4
|
+
export class Glossary {
|
5
5
|
#srcPath;
|
6
6
|
/** @type {GlossaryEntry[]} */
|
7
7
|
#srcEntries = [];
|
@@ -21,7 +21,7 @@ class Glossary {
|
|
21
21
|
}
|
22
22
|
}
|
23
23
|
|
24
|
-
class GlossaryEntry {
|
24
|
+
export class GlossaryEntry {
|
25
25
|
#srcPath;
|
26
26
|
#fileName;
|
27
27
|
#term;
|
@@ -48,5 +48,3 @@ class GlossaryEntry {
|
|
48
48
|
return this.#term;
|
49
49
|
}
|
50
50
|
}
|
51
|
-
|
52
|
-
export { Glossary };
|
package/lib/program.js
CHANGED
@@ -3,6 +3,14 @@ import { Files } from "./files.js";
|
|
3
3
|
import { CSV } from "./csv.js";
|
4
4
|
import path from "node:path";
|
5
5
|
|
6
|
+
/**
|
7
|
+
* @typedef {{
|
8
|
+
datadir: string;
|
9
|
+
outputdir: string;
|
10
|
+
"show-flower-errors": boolean;
|
11
|
+
}} CommandLineOptions
|
12
|
+
*/
|
13
|
+
|
6
14
|
class Program {
|
7
15
|
/**
|
8
16
|
* @param {string} dataDir
|
@@ -16,10 +24,10 @@ class Program {
|
|
16
24
|
return true;
|
17
25
|
}
|
18
26
|
|
19
|
-
/** @type {TaxonData[]} */
|
27
|
+
/** @type {import("./index.js").TaxonData[]} */
|
20
28
|
// @ts-ignore
|
21
29
|
const includeCSV = CSV.readFile(path.join(dataDir, includeFileName));
|
22
|
-
/** @type {Object<string,TaxonData>} */
|
30
|
+
/** @type {Object<string,import("./index.js").TaxonData>} */
|
23
31
|
const include = {};
|
24
32
|
for (const row of includeCSV) {
|
25
33
|
include[row["taxon_name"]] = row;
|
package/lib/sitegenerator.js
CHANGED
@@ -14,12 +14,12 @@ class SiteGenerator {
|
|
14
14
|
}
|
15
15
|
|
16
16
|
/**
|
17
|
-
* @param {FlowerColor[]} flowerColors
|
17
|
+
* @param {import("./flowercolor.js").FlowerColor[]} flowerColors
|
18
18
|
*/
|
19
19
|
copyIllustrations(flowerColors) {
|
20
20
|
/**
|
21
21
|
* @param {string} outputDir
|
22
|
-
* @param {FlowerColor[]} flowerColors
|
22
|
+
* @param {import("./flowercolor.js").FlowerColor[]} flowerColors
|
23
23
|
*/
|
24
24
|
function createFlowerColorIcons(outputDir, flowerColors) {
|
25
25
|
// Read generic input.
|
@@ -28,7 +28,7 @@ class SiteGenerator {
|
|
28
28
|
for (const color of flowerColors) {
|
29
29
|
Files.write(
|
30
30
|
Files.join(outputDir, "f-" + color.getColorName() + ".svg"),
|
31
|
-
srcSVG.replace("#ff0", color.getColorCode())
|
31
|
+
srcSVG.replace("#ff0", color.getColorCode()),
|
32
32
|
);
|
33
33
|
}
|
34
34
|
// Delete input file.
|
@@ -69,6 +69,16 @@ class SiteGenerator {
|
|
69
69
|
mkdir(path) {
|
70
70
|
Files.mkdir(Files.join(this.#baseDir, path));
|
71
71
|
}
|
72
|
+
|
73
|
+
/**
|
74
|
+
* @param {string} content
|
75
|
+
* @param {{title:string}} attributes
|
76
|
+
* @param {string} filename
|
77
|
+
*/
|
78
|
+
// eslint-disable-next-line no-unused-vars
|
79
|
+
writeTemplate(content, attributes, filename) {
|
80
|
+
throw new Error("must be implemented by subclass");
|
81
|
+
}
|
72
82
|
}
|
73
83
|
|
74
84
|
export { SiteGenerator };
|
package/lib/taxa.js
CHANGED
@@ -7,10 +7,14 @@ import { Genera } from "./genera.js";
|
|
7
7
|
import { Taxon } from "./taxon.js";
|
8
8
|
import { Families } from "./families.js";
|
9
9
|
import { FlowerColor } from "./flowercolor.js";
|
10
|
-
import { InatPhoto } from "./inat_photo.js";
|
11
10
|
import { TaxaCSV } from "./tools/taxacsv.js";
|
12
11
|
import { ErrorLog } from "./errorlog.js";
|
13
12
|
import { Program } from "./program.js";
|
13
|
+
import { Photo } from "./photo.js";
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @typedef {{Current: string;Former: string;Type: string;}} SynonymData
|
17
|
+
*/
|
14
18
|
|
15
19
|
const FLOWER_COLORS = [
|
16
20
|
{ name: "white", color: "white" },
|
@@ -36,11 +40,11 @@ class Taxa {
|
|
36
40
|
#isSubset;
|
37
41
|
|
38
42
|
/**
|
39
|
-
* @param {Object<string,TaxonData>|true} inclusionList
|
43
|
+
* @param {Object<string,import("./index.js").TaxonData>|true} inclusionList
|
40
44
|
* @param {ErrorLog} errorLog
|
41
45
|
* @param {boolean} showFlowerErrors
|
42
|
-
* @param {function(TaxonData,Genera):Taxon} taxonFactory
|
43
|
-
* @param {TaxonData[]} [extraTaxa=[]]
|
46
|
+
* @param {function(import("./index.js").TaxonData,Genera):Taxon} taxonFactory
|
47
|
+
* @param {import("./index.js").TaxonData[]} [extraTaxa=[]]
|
44
48
|
* @param {SynonymData[]} [extraSynonyms=[]]
|
45
49
|
* @param {boolean} includePhotos
|
46
50
|
*/
|
@@ -110,9 +114,9 @@ class Taxa {
|
|
110
114
|
*/
|
111
115
|
#loadPhotosFromFile(dataDir, filename) {
|
112
116
|
if (!fs.existsSync(path.join(dataDir, filename))) return;
|
113
|
-
/** @type {InatCsvPhoto[]} */
|
117
|
+
/** @type {import("./utils/inat-tools.js").InatCsvPhoto[]} */
|
114
118
|
const csvPhotos = CSV.parseFile(dataDir, filename).map((row) => {
|
115
|
-
/** @type {InatLicenseCode} */
|
119
|
+
/** @type {import("./utils/inat-tools.js").InatLicenseCode} */
|
116
120
|
let licenseCode = "cc-by";
|
117
121
|
if (row.licenseCode === "cc-by-nc-sa") licenseCode = "cc-by-nc-sa";
|
118
122
|
else if (row.licenseCode === "cc-by-nc") licenseCode = "cc-by-nc";
|
@@ -138,7 +142,7 @@ class Taxa {
|
|
138
142
|
continue;
|
139
143
|
}
|
140
144
|
taxon.addPhoto(
|
141
|
-
new
|
145
|
+
new Photo(
|
142
146
|
csvPhoto.id,
|
143
147
|
csvPhoto.ext,
|
144
148
|
csvPhoto.licenseCode,
|
@@ -220,7 +224,7 @@ class Taxa {
|
|
220
224
|
|
221
225
|
/**
|
222
226
|
* @param {SynonymData[]} synCSV
|
223
|
-
* @param {Object<string,TaxonData>|boolean} inclusionList
|
227
|
+
* @param {Object<string,import("./index.js").TaxonData>|boolean} inclusionList
|
224
228
|
*/
|
225
229
|
#loadSyns(synCSV, inclusionList) {
|
226
230
|
for (const syn of synCSV) {
|
@@ -241,9 +245,9 @@ class Taxa {
|
|
241
245
|
}
|
242
246
|
|
243
247
|
/**
|
244
|
-
* @param {TaxonData[]} taxaCSV
|
245
|
-
* @param {Object<string,TaxonData>|true} inclusionList
|
246
|
-
* @param {function(TaxonData,Genera):Taxon} taxonFactory
|
248
|
+
* @param {import("./index.js").TaxonData[]} taxaCSV
|
249
|
+
* @param {Object<string,import("./index.js").TaxonData>|true} inclusionList
|
250
|
+
* @param {function(import("./index.js").TaxonData,Genera):Taxon} taxonFactory
|
247
251
|
* @param {Genera} genera
|
248
252
|
* @param {boolean} showFlowerErrors
|
249
253
|
*/
|
@@ -251,7 +255,7 @@ class Taxa {
|
|
251
255
|
for (const row of taxaCSV) {
|
252
256
|
const name = row["taxon_name"];
|
253
257
|
|
254
|
-
/** @type {TaxonData|{status?:StatusCode}} */
|
258
|
+
/** @type {import("./index.js").TaxonData|{status?:import("./taxon.js").StatusCode}} */
|
255
259
|
let taxon_overrides = {};
|
256
260
|
if (inclusionList !== true) {
|
257
261
|
taxon_overrides = inclusionList[name];
|
package/lib/taxon.js
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
import { HTML } from "./html.js";
|
2
2
|
import { RarePlants } from "./rareplants.js";
|
3
3
|
|
4
|
+
/**
|
5
|
+
* @typedef {"N" | "NC" | "U" | "X"} StatusCode
|
6
|
+
*/
|
7
|
+
|
4
8
|
const TAXA_COLNAMES = {
|
5
9
|
BLOOM_START: "bloom_start",
|
6
10
|
BLOOM_END: "bloom_end",
|
@@ -9,7 +13,6 @@ const TAXA_COLNAMES = {
|
|
9
13
|
};
|
10
14
|
|
11
15
|
class Taxon {
|
12
|
-
/** @type {Genera} */
|
13
16
|
#genera;
|
14
17
|
#name;
|
15
18
|
#genus;
|
@@ -35,12 +38,12 @@ class Taxon {
|
|
35
38
|
#rankGlobal;
|
36
39
|
/** @type {string[]} */
|
37
40
|
#synonyms = [];
|
38
|
-
/** @type {Photo[]}
|
41
|
+
/** @type {import("./photo.js").Photo[]}*/
|
39
42
|
#photos = [];
|
40
43
|
|
41
44
|
/**
|
42
|
-
* @param {TaxonData} data
|
43
|
-
* @param {Genera} genera
|
45
|
+
* @param {import("./index.js").TaxonData} data
|
46
|
+
* @param {import("./genera.js").Genera} genera
|
44
47
|
*/
|
45
48
|
constructor(data, genera) {
|
46
49
|
this.#genera = genera;
|
@@ -98,12 +101,15 @@ class Taxon {
|
|
98
101
|
}
|
99
102
|
|
100
103
|
/**
|
101
|
-
* @param {
|
104
|
+
* @param {import("./photo.js").Photo} photo
|
102
105
|
*/
|
103
106
|
addPhoto(photo) {
|
104
107
|
this.#photos = this.#photos.concat([photo]);
|
105
108
|
}
|
106
109
|
|
110
|
+
/**
|
111
|
+
* @returns {import("./photo.js").Photo[]}
|
112
|
+
*/
|
107
113
|
getPhotos() {
|
108
114
|
return this.#photos;
|
109
115
|
}
|
@@ -325,7 +331,7 @@ class Taxon {
|
|
325
331
|
}
|
326
332
|
|
327
333
|
/**
|
328
|
-
* @param {Config} config
|
334
|
+
* @param {import("./config.js").Config} config
|
329
335
|
* @returns {string}
|
330
336
|
*/
|
331
337
|
getStatusDescription(config) {
|
package/lib/tools/calflora.js
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import * as path from "path";
|
2
2
|
import { CSV } from "../csv.js";
|
3
3
|
import { Files } from "../files.js";
|
4
|
+
import { TaxaCSV } from "./taxacsv.js";
|
4
5
|
|
5
6
|
const CALFLORA_URL_ALL =
|
6
7
|
"https://www.calflora.org/app/downtext?xun=117493&table=species&format=Tab&cols=0,1,4,5,8,38,41,43&psp=lifeform::grass,Tree,Herb,Fern,Shrub,Vine!!&par=f&active=";
|
@@ -17,18 +18,27 @@ const CALFLORA_URL_COUNTY =
|
|
17
18
|
* }} CalfloraData
|
18
19
|
*/
|
19
20
|
|
20
|
-
class Calflora {
|
21
|
+
export class Calflora {
|
21
22
|
/** @type {Object<string,CalfloraData>} */
|
22
23
|
static #taxa = {};
|
23
24
|
|
24
25
|
/**
|
25
26
|
*
|
26
27
|
* @param {string} toolsDataDir
|
27
|
-
* @param {
|
28
|
+
* @param {string} dataDir
|
29
|
+
* @param {import("../taxa.js").Taxa} taxa
|
28
30
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
29
|
-
* @param {ErrorLog} errorLog
|
31
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
32
|
+
* @param {boolean} update
|
30
33
|
*/
|
31
|
-
static async analyze(
|
34
|
+
static async analyze(
|
35
|
+
toolsDataDir,
|
36
|
+
dataDir,
|
37
|
+
taxa,
|
38
|
+
exceptions,
|
39
|
+
errorLog,
|
40
|
+
update,
|
41
|
+
) {
|
32
42
|
/**
|
33
43
|
* @param {string} url
|
34
44
|
* @param {string} targetFile
|
@@ -78,6 +88,8 @@ class Calflora {
|
|
78
88
|
this.#taxa[row["Taxon"]] = row;
|
79
89
|
}
|
80
90
|
|
91
|
+
const idsToUpdate = new Map();
|
92
|
+
|
81
93
|
for (const taxon of taxa.getTaxonList()) {
|
82
94
|
const name = taxon.getName();
|
83
95
|
if (name.includes(" unknown")) {
|
@@ -152,16 +164,21 @@ class Calflora {
|
|
152
164
|
cfID,
|
153
165
|
taxon.getCalfloraID(),
|
154
166
|
);
|
167
|
+
idsToUpdate.set(name, cfID);
|
155
168
|
}
|
156
169
|
}
|
157
170
|
|
158
171
|
this.#checkExceptions(taxa, exceptions, errorLog);
|
172
|
+
|
173
|
+
if (update) {
|
174
|
+
this.#updateIds(dataDir, idsToUpdate);
|
175
|
+
}
|
159
176
|
}
|
160
177
|
|
161
178
|
/**
|
162
|
-
* @param {Taxa} taxa
|
179
|
+
* @param {import("../taxa.js").Taxa} taxa
|
163
180
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
164
|
-
* @param {ErrorLog} errorLog
|
181
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
165
182
|
*/
|
166
183
|
static #checkExceptions(taxa, exceptions, errorLog) {
|
167
184
|
// Check the Calflora exceptions and make sure they still apply.
|
@@ -220,6 +237,22 @@ class Calflora {
|
|
220
237
|
}
|
221
238
|
}
|
222
239
|
}
|
223
|
-
}
|
224
240
|
|
225
|
-
|
241
|
+
/**
|
242
|
+
* @param {string} dataDir
|
243
|
+
* @param {Map<string,string>} idsToUpdate
|
244
|
+
*/
|
245
|
+
static #updateIds(dataDir, idsToUpdate) {
|
246
|
+
const taxa = new TaxaCSV(dataDir);
|
247
|
+
|
248
|
+
for (const taxonData of taxa.getTaxa()) {
|
249
|
+
const id = idsToUpdate.get(taxonData.taxon_name);
|
250
|
+
if (!id) {
|
251
|
+
continue;
|
252
|
+
}
|
253
|
+
taxonData["calrecnum"] = id;
|
254
|
+
}
|
255
|
+
|
256
|
+
taxa.write();
|
257
|
+
}
|
258
|
+
}
|
package/lib/tools/calscape.js
CHANGED
@@ -8,9 +8,9 @@ export class Calscape {
|
|
8
8
|
/**
|
9
9
|
* @param {string} toolsDataDir
|
10
10
|
* @param {string} dataDir
|
11
|
-
* @param {Taxa} taxa
|
11
|
+
* @param {import("../taxa.js").Taxa} taxa
|
12
12
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
13
|
-
* @param {ErrorLog} errorLog
|
13
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
14
14
|
* @param {boolean} update
|
15
15
|
*/
|
16
16
|
static async analyze(
|
@@ -58,9 +58,9 @@ export class Calscape {
|
|
58
58
|
}
|
59
59
|
|
60
60
|
/**
|
61
|
-
* @param {Taxa} taxa
|
61
|
+
* @param {import("../taxa.js").Taxa} taxa
|
62
62
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
63
|
-
* @param {ErrorLog} errorLog
|
63
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
64
64
|
*/
|
65
65
|
function checkExceptions(taxa, exceptions, errorLog) {
|
66
66
|
// Check the Calscape exceptions and make sure they still apply.
|
package/lib/tools/inat.js
CHANGED
@@ -21,9 +21,9 @@ export class INat {
|
|
21
21
|
/**
|
22
22
|
* @param {string} toolsDataDir
|
23
23
|
* @param {string} dataDir
|
24
|
-
* @param {Taxa} taxa
|
24
|
+
* @param {import("../taxa.js").Taxa} taxa
|
25
25
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
26
|
-
* @param {ErrorLog} errorLog
|
26
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
27
27
|
* @param {string} csvFileName
|
28
28
|
* @param {boolean} update
|
29
29
|
*/
|
@@ -115,9 +115,9 @@ export class INat {
|
|
115
115
|
|
116
116
|
/**
|
117
117
|
*
|
118
|
-
* @param {Taxa} taxa
|
118
|
+
* @param {import("../taxa.js").Taxa} taxa
|
119
119
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
120
|
-
* @param {ErrorLog} errorLog
|
120
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
121
121
|
*/
|
122
122
|
static #checkExceptions(taxa, exceptions, errorLog) {
|
123
123
|
// Check the iNat exceptions and make sure they still apply.
|
@@ -168,9 +168,9 @@ export class INat {
|
|
168
168
|
|
169
169
|
/**
|
170
170
|
*
|
171
|
-
* @param {Taxa} taxa
|
171
|
+
* @param {import("../taxa.js").Taxa} taxa
|
172
172
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
173
|
-
* @param {ErrorLog} errorLog
|
173
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
174
174
|
* @param {string} name
|
175
175
|
* @param {string} iNatName
|
176
176
|
*/
|
@@ -255,7 +255,7 @@ export class INat {
|
|
255
255
|
|
256
256
|
/**
|
257
257
|
* @param {{name:string,rank:string}} iNatResult
|
258
|
-
* @param {ErrorLog} errorLog
|
258
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
259
259
|
*/
|
260
260
|
static makeSynonymName(iNatResult, errorLog) {
|
261
261
|
const synParts = iNatResult.name.split(" ");
|
@@ -1,6 +1,7 @@
|
|
1
1
|
import { scrape } from "@htmltools/scrape";
|
2
2
|
import { Files } from "../files.js";
|
3
3
|
import { SynCSV } from "./syncsv.js";
|
4
|
+
import { TaxaCSV } from "./taxacsv.js";
|
4
5
|
|
5
6
|
/**
|
6
7
|
* @typedef {{
|
@@ -53,8 +54,8 @@ export class JepsonEFlora {
|
|
53
54
|
|
54
55
|
/**
|
55
56
|
* @param {string} toolsDataDir
|
56
|
-
* @param {Taxa} taxa
|
57
|
-
* @param {ErrorLog} errorLog
|
57
|
+
* @param {import("../taxa.js").Taxa} taxa
|
58
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
58
59
|
*/
|
59
60
|
constructor(toolsDataDir, taxa, errorLog) {
|
60
61
|
this.#toolsDataPath = toolsDataDir + "/jepson-eflora";
|
@@ -63,16 +64,19 @@ export class JepsonEFlora {
|
|
63
64
|
}
|
64
65
|
|
65
66
|
/**
|
67
|
+
* @param {string} dataDir
|
66
68
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
67
69
|
* @param {boolean} update
|
68
70
|
*/
|
69
|
-
async analyze(exceptions, update) {
|
71
|
+
async analyze(dataDir, exceptions, update) {
|
70
72
|
// Create data directory if it's not there.
|
71
73
|
Files.mkdir(this.#toolsDataPath);
|
72
74
|
|
73
75
|
// Retrieve all Jepson indexes.
|
74
76
|
await this.#loadIndexPages();
|
75
77
|
|
78
|
+
const idsToUpdate = new Map();
|
79
|
+
|
76
80
|
for (const taxon of this.#taxa.getTaxonList()) {
|
77
81
|
const name = taxon.getName();
|
78
82
|
if (name.includes(" unknown")) {
|
@@ -95,6 +99,7 @@ export class JepsonEFlora {
|
|
95
99
|
taxon.getJepsonID(),
|
96
100
|
jepsInfo.id,
|
97
101
|
);
|
102
|
+
idsToUpdate.set(name, jepsInfo.id);
|
98
103
|
}
|
99
104
|
|
100
105
|
const efStatus = this.#getStatusCode(jepsInfo);
|
@@ -116,6 +121,7 @@ export class JepsonEFlora {
|
|
116
121
|
this.#checkExceptions(exceptions);
|
117
122
|
|
118
123
|
if (update) {
|
124
|
+
this.#updateIds(dataDir, idsToUpdate);
|
119
125
|
this.#updateSynCSV();
|
120
126
|
}
|
121
127
|
}
|
@@ -222,7 +228,7 @@ export class JepsonEFlora {
|
|
222
228
|
|
223
229
|
/**
|
224
230
|
* @param {JepsonTaxon} jepsInfo
|
225
|
-
* @returns {StatusCode|undefined}
|
231
|
+
* @returns {import("../taxon.js").StatusCode|undefined}
|
226
232
|
*/
|
227
233
|
#getStatusCode(jepsInfo) {
|
228
234
|
switch (jepsInfo.type) {
|
@@ -396,6 +402,24 @@ export class JepsonEFlora {
|
|
396
402
|
}
|
397
403
|
}
|
398
404
|
|
405
|
+
/**
|
406
|
+
* @param {string} dataDir
|
407
|
+
* @param {Map<string,string>} idsToUpdate
|
408
|
+
*/
|
409
|
+
#updateIds(dataDir, idsToUpdate) {
|
410
|
+
const taxa = new TaxaCSV(dataDir);
|
411
|
+
|
412
|
+
for (const taxonData of taxa.getTaxa()) {
|
413
|
+
const id = idsToUpdate.get(taxonData.taxon_name);
|
414
|
+
if (!id) {
|
415
|
+
continue;
|
416
|
+
}
|
417
|
+
taxonData["jepson id"] = id;
|
418
|
+
}
|
419
|
+
|
420
|
+
taxa.write();
|
421
|
+
}
|
422
|
+
|
399
423
|
#updateSynCSV() {
|
400
424
|
const csv = new SynCSV("./data");
|
401
425
|
const data = csv.getData();
|
@@ -0,0 +1,102 @@
|
|
1
|
+
import path from "node:path";
|
2
|
+
import { Files } from "../files.js";
|
3
|
+
import { scrape } from "@htmltools/scrape";
|
4
|
+
|
5
|
+
export class JepsonFamilies {
|
6
|
+
/**
|
7
|
+
* @param {string} toolsDataDir
|
8
|
+
* @param {string} outputdir
|
9
|
+
*/
|
10
|
+
static async build(toolsDataDir, outputdir) {
|
11
|
+
const url = "https://ucjeps.berkeley.edu/eflora/toc.html";
|
12
|
+
const indexFileName = path.basename(url);
|
13
|
+
const toolsDataPath = toolsDataDir + "/jepsonfam";
|
14
|
+
const indexFilePath = toolsDataPath + "/" + indexFileName;
|
15
|
+
|
16
|
+
// Create data directory if it's not there.
|
17
|
+
Files.mkdir(toolsDataPath);
|
18
|
+
|
19
|
+
// Download the data file if it doesn't exist.
|
20
|
+
if (!Files.exists(indexFilePath)) {
|
21
|
+
console.log("retrieving Jepson family index");
|
22
|
+
await Files.fetch(url, indexFilePath);
|
23
|
+
}
|
24
|
+
|
25
|
+
const document = scrape.parseFile(indexFilePath);
|
26
|
+
|
27
|
+
const body = scrape.getSubtree(document, (t) => t.tagName === "body");
|
28
|
+
if (!body) {
|
29
|
+
throw new Error();
|
30
|
+
}
|
31
|
+
const contentDiv = scrape.getSubtree(
|
32
|
+
body,
|
33
|
+
(t) => scrape.getAttr(t, "id") === "content",
|
34
|
+
);
|
35
|
+
if (!contentDiv) {
|
36
|
+
throw new Error();
|
37
|
+
}
|
38
|
+
const rows = scrape.getSubtrees(contentDiv, (t) => t.tagName === "tr");
|
39
|
+
|
40
|
+
this.#parseRows(outputdir, rows);
|
41
|
+
}
|
42
|
+
|
43
|
+
/**
|
44
|
+
* @param {string} toolsDataPath
|
45
|
+
* @param {import("@htmltools/scrape").Element[]} rows
|
46
|
+
*/
|
47
|
+
static #parseRows(toolsDataPath, rows) {
|
48
|
+
/** @type {Object<string,{section:string,id:string}>} */
|
49
|
+
const families = {};
|
50
|
+
/** @type {Object<string,{family:string,id:string}>} */
|
51
|
+
const genera = {};
|
52
|
+
|
53
|
+
for (const row of rows) {
|
54
|
+
const cols = scrape.getSubtrees(row, (t) => t.tagName === "td");
|
55
|
+
if (!cols || cols.length < 3) {
|
56
|
+
continue;
|
57
|
+
}
|
58
|
+
|
59
|
+
// Find the section.
|
60
|
+
const section = scrape.getTextContent(cols[0].children[0]);
|
61
|
+
|
62
|
+
// Find the family name and ID.
|
63
|
+
const familyLink = cols[1].children[0];
|
64
|
+
if (familyLink.type !== "element") {
|
65
|
+
throw new Error();
|
66
|
+
}
|
67
|
+
const familyTarget = scrape.getAttr(familyLink, "href");
|
68
|
+
if (!familyTarget) {
|
69
|
+
throw new Error();
|
70
|
+
}
|
71
|
+
const familyID = familyTarget.split("=")[1];
|
72
|
+
const familyName = scrape.getTextContent(familyLink.children[0]);
|
73
|
+
families[familyName] = { section: section, id: familyID };
|
74
|
+
|
75
|
+
// Find all the genera.
|
76
|
+
const genusLinks = scrape.getSubtrees(
|
77
|
+
cols[2],
|
78
|
+
(t) => t.tagName === "a",
|
79
|
+
);
|
80
|
+
for (const genusLink of genusLinks) {
|
81
|
+
const genusTarget = scrape.getAttr(genusLink, "href");
|
82
|
+
if (!genusTarget) {
|
83
|
+
throw new Error();
|
84
|
+
}
|
85
|
+
const genusID = genusTarget.split("=")[1];
|
86
|
+
const genusName = scrape.getTextContent(genusLink.children[0]);
|
87
|
+
genera[genusName] = { family: familyName, id: genusID };
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
Files.write(
|
92
|
+
toolsDataPath + "/families.json",
|
93
|
+
JSON.stringify(families, undefined, 4),
|
94
|
+
true,
|
95
|
+
);
|
96
|
+
Files.write(
|
97
|
+
toolsDataPath + "/genera.json",
|
98
|
+
JSON.stringify(genera, undefined, 4),
|
99
|
+
true,
|
100
|
+
);
|
101
|
+
}
|
102
|
+
}
|