@ca-plant-list/ca-plant-list 0.4.21 → 0.4.23
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/exceptions.json +22 -1
- package/data/synonyms.csv +2 -0
- package/data/taxa.csv +1754 -1753
- package/lib/basepagerenderer.js +10 -4
- package/lib/ebook/images.js +3 -3
- package/lib/ebook/pages/{page_list_families.js → pageListFamilies.js} +1 -1
- package/lib/ebook/pages/{page_list_flowers.js → pageListFlowers.js} +2 -2
- package/lib/ebook/pages/page_list_species.js +1 -1
- package/lib/ebook/pages/taxonpage.js +1 -1
- package/lib/ebook/pages/tocpage.js +2 -2
- package/lib/ebook/plantbook.js +3 -3
- package/lib/errorlog.js +1 -1
- package/lib/externalsites.js +113 -35
- package/lib/files.js +3 -5
- package/lib/flowercolor.js +2 -2
- package/lib/genera.js +4 -4
- package/lib/html.js +7 -8
- package/lib/htmltaxon.js +122 -33
- package/lib/index.d.ts +72 -27
- package/lib/index.js +3 -3
- package/lib/pagerenderer.js +6 -6
- package/lib/taxonomy/families.js +104 -0
- package/lib/{taxa.js → taxonomy/taxa.js} +18 -18
- package/lib/{taxon.js → taxonomy/taxon.js} +41 -111
- package/lib/taxonomy/taxonomy.js +17 -0
- package/lib/tools/calflora.js +2 -2
- package/lib/tools/calscape.js +3 -3
- package/lib/tools/cch2.js +128 -10
- package/lib/tools/fna.js +163 -0
- package/lib/tools/inat.js +3 -3
- package/lib/tools/jepsoneflora.js +21 -2
- package/lib/tools/rpi.js +5 -5
- package/lib/tools/supplementaltext.js +1 -1
- package/lib/tools/taxacsv.js +23 -4
- package/lib/types.js +10 -0
- package/lib/utils/inat-tools.js +2 -2
- package/lib/web/pageFamily.js +146 -0
- package/lib/web/pagetaxon.js +21 -63
- package/package.json +2 -1
- package/scripts/build-ebook.js +4 -4
- package/scripts/build-site.js +3 -3
- package/scripts/cpl-photos.js +1 -1
- package/scripts/cpl-tools.js +18 -2
- package/scripts/inatobsphotos.js +2 -2
- package/scripts/inattaxonphotos.js +2 -2
- package/lib/families.js +0 -243
- package/lib/jepson.js +0 -17
package/lib/tools/fna.js
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
import path from "node:path";
|
2
|
+
import { Files } from "../files.js";
|
3
|
+
import { scrape } from "@htmltools/scrape";
|
4
|
+
import { TaxaCSV } from "./taxacsv.js";
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @typedef {{name:string}} FNATaxon
|
8
|
+
* @typedef {Map<string,FNATaxon>} FNATaxa
|
9
|
+
*/
|
10
|
+
|
11
|
+
export class FNA {
|
12
|
+
/**
|
13
|
+
* @param {string} toolsDataDir
|
14
|
+
* @param {string} dataDir
|
15
|
+
* @param {import("../types.js").Taxa} taxa
|
16
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
17
|
+
* @param {boolean} update
|
18
|
+
*/
|
19
|
+
static async analyze(toolsDataDir, dataDir, taxa, errorLog, update) {
|
20
|
+
const toolsDataPath = path.join(toolsDataDir, "fna");
|
21
|
+
const fnaTaxa = await getFNATaxa(toolsDataPath);
|
22
|
+
|
23
|
+
const namesToUpdate = new Map();
|
24
|
+
|
25
|
+
for (const taxon of taxa.getTaxonList()) {
|
26
|
+
const fnaName = getFNAName(taxon, fnaTaxa) ?? "";
|
27
|
+
const taxonFNAName = taxon.getFNAName();
|
28
|
+
if (fnaName !== taxonFNAName) {
|
29
|
+
const name = taxon.getName();
|
30
|
+
errorLog.log(
|
31
|
+
name,
|
32
|
+
"FNA name does not match name from FNA data",
|
33
|
+
taxonFNAName,
|
34
|
+
fnaName,
|
35
|
+
);
|
36
|
+
namesToUpdate.set(name, fnaName === name ? "true" : fnaName);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
if (update) {
|
41
|
+
updateNames(dataDir, namesToUpdate);
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
/**
|
47
|
+
* @param {import("../types.js").Taxon} taxon
|
48
|
+
* @param {FNATaxa} fnaTaxa
|
49
|
+
* @returns {string|undefined}
|
50
|
+
*/
|
51
|
+
function getFNAName(taxon, fnaTaxa) {
|
52
|
+
/**
|
53
|
+
* @param {string} input
|
54
|
+
* @returns {string|undefined}
|
55
|
+
*/
|
56
|
+
function getName(input) {
|
57
|
+
if (fnaTaxa.has(input)) {
|
58
|
+
return input;
|
59
|
+
}
|
60
|
+
|
61
|
+
// See if we can swap var./subsp. to find it.
|
62
|
+
const parts = input.split(" ");
|
63
|
+
if (parts.length === 4) {
|
64
|
+
parts[2] = parts[2] === "var." ? "subsp." : "var.";
|
65
|
+
input = parts.join(" ");
|
66
|
+
if (fnaTaxa.has(input)) {
|
67
|
+
return input;
|
68
|
+
}
|
69
|
+
}
|
70
|
+
|
71
|
+
// If it's the nominate subsp/var, see if we can find the species.
|
72
|
+
if (parts.length === 4 && parts[1] === parts[3]) {
|
73
|
+
input = parts[0] + " " + parts[1];
|
74
|
+
if (fnaTaxa.has(input)) {
|
75
|
+
return input;
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
const name = getName(taxon.getName());
|
80
|
+
if (name !== undefined) {
|
81
|
+
return name;
|
82
|
+
}
|
83
|
+
|
84
|
+
// See if any synonyms match.
|
85
|
+
for (const synonym of taxon.getSynonyms()) {
|
86
|
+
const name = getName(synonym);
|
87
|
+
if (name !== undefined) {
|
88
|
+
return name;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* @param {string} toolsDataPath
|
95
|
+
* @returns {Promise<FNATaxa>}
|
96
|
+
*/
|
97
|
+
async function getFNATaxa(toolsDataPath) {
|
98
|
+
/** @type {FNATaxa} */
|
99
|
+
const fnaTaxa = new Map();
|
100
|
+
|
101
|
+
Files.mkdir(toolsDataPath);
|
102
|
+
|
103
|
+
// Get list of volumes.
|
104
|
+
const volumePage = path.join(toolsDataPath, "volumes.html");
|
105
|
+
if (!Files.exists(volumePage)) {
|
106
|
+
await Files.fetch(
|
107
|
+
"http://floranorthamerica.org/Special:SearchByProperty/:Volume/",
|
108
|
+
volumePage,
|
109
|
+
);
|
110
|
+
}
|
111
|
+
|
112
|
+
const volDoc = scrape.parseFile(volumePage);
|
113
|
+
const links = scrape.getSubtrees(volDoc, (e) => {
|
114
|
+
const href = scrape.getAttr(e, "href");
|
115
|
+
return href !== undefined && href.startsWith("/Volume_");
|
116
|
+
});
|
117
|
+
const vols = links.map((e) => scrape.getTextContent(e));
|
118
|
+
|
119
|
+
// For each volume, retrieve the JSON.
|
120
|
+
const baseURL =
|
121
|
+
"http://floranorthamerica.org/Special:Ask/limit=5000/unescape=true/format=json";
|
122
|
+
for (const vol of vols) {
|
123
|
+
const fileName = path.join(toolsDataPath, `${vol}.json`);
|
124
|
+
if (!Files.exists(fileName)) {
|
125
|
+
const url = baseURL + `/-5B-5BVolume::${vol}-5D-5D`;
|
126
|
+
await Files.fetch(url, fileName);
|
127
|
+
}
|
128
|
+
|
129
|
+
const text = Files.read(fileName);
|
130
|
+
/** @type {{results:Object<string,{}>}} */
|
131
|
+
const json = JSON.parse(text);
|
132
|
+
const results = json.results;
|
133
|
+
|
134
|
+
// If there are more than 5000 results, this will need to be updated to retrieve chunks.
|
135
|
+
if (Object.entries(results).length >= 5000) {
|
136
|
+
throw new Error(`${vol} has more than 5000 results`);
|
137
|
+
}
|
138
|
+
|
139
|
+
for (const [k] of Object.entries(results)) {
|
140
|
+
fnaTaxa.set(k, { name: k });
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
return fnaTaxa;
|
145
|
+
}
|
146
|
+
|
147
|
+
/**
|
148
|
+
* @param {string} dataDir
|
149
|
+
* @param {Map<string,string>} namesToUpdate
|
150
|
+
*/
|
151
|
+
function updateNames(dataDir, namesToUpdate) {
|
152
|
+
const taxa = new TaxaCSV(dataDir);
|
153
|
+
|
154
|
+
for (const taxonData of taxa.getTaxa()) {
|
155
|
+
const newName = namesToUpdate.get(taxonData.taxon_name);
|
156
|
+
if (!newName) {
|
157
|
+
continue;
|
158
|
+
}
|
159
|
+
taxonData.fna = newName;
|
160
|
+
}
|
161
|
+
|
162
|
+
taxa.write();
|
163
|
+
}
|
package/lib/tools/inat.js
CHANGED
@@ -21,7 +21,7 @@ export class INat {
|
|
21
21
|
/**
|
22
22
|
* @param {string} toolsDataDir
|
23
23
|
* @param {string} dataDir
|
24
|
-
* @param {import("../
|
24
|
+
* @param {import("../types.js").Taxa} taxa
|
25
25
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
26
26
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
27
27
|
* @param {string} csvFileName
|
@@ -115,7 +115,7 @@ export class INat {
|
|
115
115
|
|
116
116
|
/**
|
117
117
|
*
|
118
|
-
* @param {import("../
|
118
|
+
* @param {import("../types.js").Taxa} taxa
|
119
119
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
120
120
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
121
121
|
*/
|
@@ -168,7 +168,7 @@ export class INat {
|
|
168
168
|
|
169
169
|
/**
|
170
170
|
*
|
171
|
-
* @param {import("../
|
171
|
+
* @param {import("../types.js").Taxa} taxa
|
172
172
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
173
173
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
174
174
|
* @param {string} name
|
@@ -54,7 +54,7 @@ export class JepsonEFlora {
|
|
54
54
|
|
55
55
|
/**
|
56
56
|
* @param {string} toolsDataDir
|
57
|
-
* @param {import("../taxa.js").Taxa} taxa
|
57
|
+
* @param {import("../taxonomy/taxa.js").Taxa} taxa
|
58
58
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
59
59
|
*/
|
60
60
|
constructor(toolsDataDir, taxa, errorLog) {
|
@@ -102,8 +102,25 @@ export class JepsonEFlora {
|
|
102
102
|
idsToUpdate.set(name, jepsInfo.id);
|
103
103
|
}
|
104
104
|
|
105
|
-
|
105
|
+
let efStatus = this.#getStatusCode(jepsInfo);
|
106
106
|
const taxonStatus = taxon.getStatus();
|
107
|
+
// Override if exception is specified.
|
108
|
+
const nativeException = exceptions.getValue(
|
109
|
+
name,
|
110
|
+
"jepson",
|
111
|
+
"native",
|
112
|
+
);
|
113
|
+
if (typeof nativeException === "boolean") {
|
114
|
+
const overrideStatus = nativeException ? "N" : "X";
|
115
|
+
if (overrideStatus === efStatus) {
|
116
|
+
this.#errorLog.log(
|
117
|
+
name,
|
118
|
+
"has unnecessary Jepson native override",
|
119
|
+
);
|
120
|
+
}
|
121
|
+
efStatus = overrideStatus;
|
122
|
+
}
|
123
|
+
|
107
124
|
if (
|
108
125
|
efStatus !== taxonStatus &&
|
109
126
|
!(taxonStatus === "NC" && efStatus === "N")
|
@@ -154,6 +171,8 @@ export class JepsonEFlora {
|
|
154
171
|
for (const [k] of Object.entries(exceptions)) {
|
155
172
|
const jepsonData = this.#nameInfo.get(name);
|
156
173
|
switch (k) {
|
174
|
+
case "native":
|
175
|
+
break;
|
157
176
|
case "notineflora":
|
158
177
|
// Make sure it is really not in eFlora.
|
159
178
|
if (jepsonData) {
|
package/lib/tools/rpi.js
CHANGED
@@ -12,7 +12,7 @@ class RPI {
|
|
12
12
|
|
13
13
|
/**
|
14
14
|
* @param {string} toolsDataDir
|
15
|
-
* @param {import("../
|
15
|
+
* @param {import("../types.js").Taxa} taxa
|
16
16
|
* @param {import("../config.js").Config} config
|
17
17
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
18
18
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
@@ -80,8 +80,8 @@ class RPI {
|
|
80
80
|
const rank = row["CRPR"];
|
81
81
|
const rawCESA = row["CESA"];
|
82
82
|
const rawFESA = row["FESA"];
|
83
|
-
const cesa = rawCESA === "None" ?
|
84
|
-
const fesa = rawFESA === "None" ?
|
83
|
+
const cesa = rawCESA === "None" ? "" : rawCESA;
|
84
|
+
const fesa = rawFESA === "None" ? "" : rawFESA;
|
85
85
|
const cnddb = row["SRank"];
|
86
86
|
const globalRank = row["GRank"];
|
87
87
|
const taxon = taxa.getTaxon(name);
|
@@ -202,7 +202,7 @@ class RPI {
|
|
202
202
|
}
|
203
203
|
|
204
204
|
/**
|
205
|
-
* @param {import("../
|
205
|
+
* @param {import("../types.js").Taxa} taxa
|
206
206
|
* @param {import("../config.js").Config} config
|
207
207
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
208
208
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
@@ -366,7 +366,7 @@ class RPI {
|
|
366
366
|
|
367
367
|
/**
|
368
368
|
* @param {string} toolsDataDir
|
369
|
-
* @param {import("../
|
369
|
+
* @param {import("../types.js").Taxa} taxa
|
370
370
|
* @param {import("../exceptions.js").Exceptions} exceptions
|
371
371
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
372
372
|
*/
|
@@ -5,7 +5,7 @@ const VALID_EXTENSIONS = new Set(["md", "footer.md"]);
|
|
5
5
|
export class SupplementalText {
|
6
6
|
/**
|
7
7
|
*
|
8
|
-
* @param {import("../
|
8
|
+
* @param {import("../types.js").Taxa} taxa
|
9
9
|
* @param {import("../errorlog.js").ErrorLog} errorLog
|
10
10
|
*/
|
11
11
|
static analyze(taxa, errorLog) {
|
package/lib/tools/taxacsv.js
CHANGED
@@ -1,9 +1,30 @@
|
|
1
1
|
import path from "path";
|
2
2
|
import { CSV } from "../csv.js";
|
3
3
|
|
4
|
+
const HEADERS = [
|
5
|
+
"taxon_name",
|
6
|
+
"common name",
|
7
|
+
"status",
|
8
|
+
"jepson id",
|
9
|
+
"calrecnum",
|
10
|
+
"inat id",
|
11
|
+
"cch2_id",
|
12
|
+
"fna",
|
13
|
+
"calscape_cn",
|
14
|
+
"life_cycle",
|
15
|
+
"flower_color",
|
16
|
+
"bloom_start",
|
17
|
+
"bloom_end",
|
18
|
+
"RPI ID",
|
19
|
+
"CRPR",
|
20
|
+
"CESA",
|
21
|
+
"FESA",
|
22
|
+
"SRank",
|
23
|
+
"GRank",
|
24
|
+
];
|
25
|
+
|
4
26
|
export class TaxaCSV {
|
5
27
|
#filePath;
|
6
|
-
#headers;
|
7
28
|
/** @type {import("../index.js").TaxonData[]} */
|
8
29
|
#taxa;
|
9
30
|
|
@@ -13,9 +34,7 @@ export class TaxaCSV {
|
|
13
34
|
constructor(dataDir) {
|
14
35
|
this.#filePath = path.join(dataDir, "taxa.csv");
|
15
36
|
const csv = CSV.readFileAndHeaders(this.#filePath);
|
16
|
-
// @ts-ignore
|
17
37
|
this.#taxa = csv.data;
|
18
|
-
this.#headers = csv.headers;
|
19
38
|
}
|
20
39
|
|
21
40
|
/**
|
@@ -26,6 +45,6 @@ export class TaxaCSV {
|
|
26
45
|
}
|
27
46
|
|
28
47
|
write() {
|
29
|
-
CSV.writeFileObject(this.#filePath, this.#taxa,
|
48
|
+
CSV.writeFileObject(this.#filePath, this.#taxa, HEADERS);
|
30
49
|
}
|
31
50
|
}
|
package/lib/types.js
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
/**
|
2
|
+
* Classes
|
3
|
+
* @typedef {import("./config.js").Config} Config
|
4
|
+
* @typedef {import("./taxonomy/families.js").Families} Families
|
5
|
+
* @typedef {import("./taxonomy/families.js").Family} Family
|
6
|
+
* @typedef {import("./taxonomy/taxa.js").Taxa} Taxa
|
7
|
+
* @typedef {import("./index.js").TaxaColDef<import("./types.js").Taxon>} TaxaColDef
|
8
|
+
* @typedef {import("./taxonomy/taxon.js").Taxon} Taxon
|
9
|
+
* @typedef {import("./taxonomy/taxonomy.js").Taxonomy} Taxonomy
|
10
|
+
*/
|
package/lib/utils/inat-tools.js
CHANGED
@@ -41,7 +41,7 @@ import { chunk, sleep } from "../util.js";
|
|
41
41
|
const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
|
42
42
|
|
43
43
|
/**
|
44
|
-
* @param {import("../
|
44
|
+
* @param {import("../types.js").Taxon[]} taxa
|
45
45
|
* @return {Promise<InatApiTaxon[]>}
|
46
46
|
*/
|
47
47
|
async function fetchInatTaxa(taxa) {
|
@@ -57,7 +57,7 @@ async function fetchInatTaxa(taxa) {
|
|
57
57
|
}
|
58
58
|
|
59
59
|
/**
|
60
|
-
* @param {import("../
|
60
|
+
* @param {import("../types.js").Taxon[]} taxaToUpdate
|
61
61
|
* @returns {Promise<Map<string,InatPhotoInfo[]>>}
|
62
62
|
*/
|
63
63
|
export async function getTaxonPhotos(taxaToUpdate) {
|
@@ -0,0 +1,146 @@
|
|
1
|
+
import { ExternalSites } from "../externalsites.js";
|
2
|
+
import { GenericPage } from "../genericpage.js";
|
3
|
+
import { HTML } from "../html.js";
|
4
|
+
import { HTMLTaxon } from "../htmltaxon.js";
|
5
|
+
import { Sections } from "../taxonomy/families.js";
|
6
|
+
|
7
|
+
export class PageFamilyList extends GenericPage {
|
8
|
+
#families;
|
9
|
+
|
10
|
+
/**
|
11
|
+
* @param {string} outputDir
|
12
|
+
* @param {import("../types.js").Family[]} families
|
13
|
+
*/
|
14
|
+
constructor(outputDir, families) {
|
15
|
+
super(outputDir, "Families", "list_families");
|
16
|
+
this.#families = families;
|
17
|
+
}
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @param {import("../types.js").TaxaColDef[]} [taxaColumns]
|
21
|
+
*/
|
22
|
+
render(taxaColumns) {
|
23
|
+
let html = this.getDefaultIntro();
|
24
|
+
|
25
|
+
const sections = Sections.getSections();
|
26
|
+
const sectionLinks = [];
|
27
|
+
for (const name of Object.keys(sections).sort()) {
|
28
|
+
const taxa = sections[name];
|
29
|
+
|
30
|
+
// Render the section page.
|
31
|
+
new PageSection(this.getOutputDir(), name, taxa).render(
|
32
|
+
taxaColumns,
|
33
|
+
);
|
34
|
+
|
35
|
+
// Render the link.
|
36
|
+
const href = "./" + name + ".html";
|
37
|
+
sectionLinks.push(
|
38
|
+
HTML.getLink(href, name) + " (" + taxa.length + ")",
|
39
|
+
);
|
40
|
+
}
|
41
|
+
html += HTML.wrap("ul", HTML.arrayToLI(sectionLinks), {
|
42
|
+
class: "listmenu",
|
43
|
+
});
|
44
|
+
|
45
|
+
html += "<table>";
|
46
|
+
html += "<thead>";
|
47
|
+
html += HTML.textElement("th", "Family");
|
48
|
+
html += HTML.textElement("th", "Number of Species", { class: "right" });
|
49
|
+
html += "</thead>";
|
50
|
+
|
51
|
+
html += "<tbody>";
|
52
|
+
for (const family of this.#families) {
|
53
|
+
const taxa = family.getTaxa();
|
54
|
+
if (!taxa) {
|
55
|
+
continue;
|
56
|
+
}
|
57
|
+
let cols = HTML.wrap(
|
58
|
+
"td",
|
59
|
+
HTML.getLink("./" + family.getFileName(), family.getName()),
|
60
|
+
);
|
61
|
+
cols += HTML.wrap("td", taxa.length, { class: "right" });
|
62
|
+
html += HTML.wrap("tr", cols);
|
63
|
+
}
|
64
|
+
html += "</tbody>";
|
65
|
+
|
66
|
+
html += "</table>";
|
67
|
+
|
68
|
+
this.writeFile(html);
|
69
|
+
}
|
70
|
+
|
71
|
+
/**
|
72
|
+
* @param {string} outputDir
|
73
|
+
* @param {import("../types.js").TaxaColDef[]} [taxaColumns]
|
74
|
+
*/
|
75
|
+
renderPages(outputDir, taxaColumns) {
|
76
|
+
for (const family of this.#families) {
|
77
|
+
if (family.getTaxa()) {
|
78
|
+
new PageFamily(outputDir, family).render(taxaColumns);
|
79
|
+
}
|
80
|
+
}
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
class PageFamily extends GenericPage {
|
85
|
+
#family;
|
86
|
+
|
87
|
+
/**
|
88
|
+
* @param {string} outputDir
|
89
|
+
* @param {import("../types.js").Family} family
|
90
|
+
*/
|
91
|
+
constructor(outputDir, family) {
|
92
|
+
super(outputDir, family.getName(), family.getBaseFileName());
|
93
|
+
this.#family = family;
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* @param {import("../types.js").TaxaColDef[]} [columns]
|
98
|
+
*/
|
99
|
+
render(columns) {
|
100
|
+
let html = this.getDefaultIntro();
|
101
|
+
|
102
|
+
const jepsonLink = ExternalSites.getJepsonRefLink(this.#family);
|
103
|
+
if (jepsonLink) {
|
104
|
+
html += HTML.wrap(
|
105
|
+
"div",
|
106
|
+
HTML.getLink(jepsonLink, "Jepson eFlora", {}, true),
|
107
|
+
{
|
108
|
+
class: "section",
|
109
|
+
},
|
110
|
+
);
|
111
|
+
}
|
112
|
+
|
113
|
+
const taxa = this.#family.getTaxa();
|
114
|
+
if (!taxa) {
|
115
|
+
throw new Error();
|
116
|
+
}
|
117
|
+
html += HTMLTaxon.getTaxaTable(taxa, columns);
|
118
|
+
|
119
|
+
this.writeFile(html);
|
120
|
+
}
|
121
|
+
}
|
122
|
+
|
123
|
+
class PageSection extends GenericPage {
|
124
|
+
#taxa;
|
125
|
+
|
126
|
+
/**
|
127
|
+
* @param {string} outputDir
|
128
|
+
* @param {string} name
|
129
|
+
* @param {import("../types.js").Taxon[]} taxa
|
130
|
+
*/
|
131
|
+
constructor(outputDir, name, taxa) {
|
132
|
+
super(outputDir, name, name);
|
133
|
+
this.#taxa = taxa;
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* @param {import("../types.js").TaxaColDef[]} [columns]
|
138
|
+
*/
|
139
|
+
render(columns) {
|
140
|
+
let html = this.getDefaultIntro();
|
141
|
+
|
142
|
+
html += HTMLTaxon.getTaxaTable(this.#taxa, columns);
|
143
|
+
|
144
|
+
this.writeFile(html);
|
145
|
+
}
|
146
|
+
}
|
package/lib/web/pagetaxon.js
CHANGED
@@ -1,7 +1,5 @@
|
|
1
|
-
import { Jepson } from "../jepson.js";
|
2
1
|
import { RarePlants } from "../rareplants.js";
|
3
2
|
import { GenericPage } from "../genericpage.js";
|
4
|
-
import { ExternalSites } from "../externalsites.js";
|
5
3
|
import { HTML } from "../html.js";
|
6
4
|
import { HTMLTaxon } from "../htmltaxon.js";
|
7
5
|
|
@@ -12,7 +10,7 @@ export class PageTaxon extends GenericPage {
|
|
12
10
|
/**
|
13
11
|
* @param {string} outputDir
|
14
12
|
* @param {import("../config.js").Config} config
|
15
|
-
* @param {import("../
|
13
|
+
* @param {import("../types.js").Taxon} taxon
|
16
14
|
*/
|
17
15
|
constructor(outputDir, config, taxon) {
|
18
16
|
super(outputDir, taxon.getName(), taxon.getBaseFileName());
|
@@ -21,72 +19,31 @@ export class PageTaxon extends GenericPage {
|
|
21
19
|
}
|
22
20
|
|
23
21
|
#getInfoLinks() {
|
22
|
+
/** @type {string[]} */
|
24
23
|
const links = [];
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
}
|
29
|
-
const cfLink = this.#taxon.getCalfloraTaxonLink();
|
30
|
-
if (cfLink) {
|
31
|
-
links.push(cfLink);
|
32
|
-
}
|
33
|
-
const iNatLink = this.#taxon.getINatTaxonLink();
|
34
|
-
if (iNatLink) {
|
35
|
-
links.push(iNatLink);
|
36
|
-
}
|
37
|
-
const calscapeLink = HTMLTaxon.getCalscapeLink(this.#taxon);
|
38
|
-
if (calscapeLink) {
|
39
|
-
links.push(calscapeLink);
|
40
|
-
}
|
41
|
-
const rpiLink = this.#taxon.getRPITaxonLink();
|
42
|
-
if (rpiLink) {
|
43
|
-
links.push(rpiLink);
|
44
|
-
}
|
45
|
-
HTMLTaxon.addLink(
|
24
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "jepson");
|
25
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "calflora");
|
26
|
+
HTMLTaxon.addRefLink(
|
46
27
|
links,
|
47
|
-
|
48
|
-
"
|
28
|
+
this.#taxon,
|
29
|
+
"inat",
|
30
|
+
this.#taxon.getINatSyn()
|
31
|
+
? " (" + this.#taxon.getINatSyn() + ")"
|
32
|
+
: "",
|
49
33
|
);
|
34
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "rpi");
|
35
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "fna");
|
36
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "cch");
|
37
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "calscape");
|
50
38
|
return links;
|
51
39
|
}
|
52
40
|
|
53
41
|
#getObsLinks() {
|
42
|
+
/** @type {string[]} */
|
54
43
|
const links = [];
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
this.#config.getCountyCodes().join("!") +
|
59
|
-
"&incobs=f&taxon=" +
|
60
|
-
this.#taxon.getCalfloraName().replaceAll(" ", "+"),
|
61
|
-
"Calflora",
|
62
|
-
{},
|
63
|
-
true,
|
64
|
-
),
|
65
|
-
);
|
66
|
-
const iNatID = this.#taxon.getINatID();
|
67
|
-
if (iNatID) {
|
68
|
-
links.push(
|
69
|
-
HTML.getLink(
|
70
|
-
ExternalSites.getInatObsLink({
|
71
|
-
project_id: this.#config.getConfigValue(
|
72
|
-
"inat",
|
73
|
-
"project_id",
|
74
|
-
),
|
75
|
-
subview: "map",
|
76
|
-
taxon_id: iNatID,
|
77
|
-
}),
|
78
|
-
"iNaturalist",
|
79
|
-
{},
|
80
|
-
true,
|
81
|
-
),
|
82
|
-
);
|
83
|
-
}
|
84
|
-
HTMLTaxon.addLink(
|
85
|
-
links,
|
86
|
-
ExternalSites.getCCH2ObsLink(this.#taxon, this.#config),
|
87
|
-
"CCH2",
|
88
|
-
);
|
89
|
-
|
44
|
+
HTMLTaxon.addObsLink(links, this.#taxon, this.#config, "inat");
|
45
|
+
HTMLTaxon.addObsLink(links, this.#taxon, this.#config, "calflora");
|
46
|
+
HTMLTaxon.addObsLink(links, this.#taxon, this.#config, "cch");
|
90
47
|
return links;
|
91
48
|
}
|
92
49
|
|
@@ -103,7 +60,7 @@ export class PageTaxon extends GenericPage {
|
|
103
60
|
}) +
|
104
61
|
HTML.getToolTip(
|
105
62
|
cnpsRank,
|
106
|
-
this.#taxon
|
63
|
+
HTMLTaxon.getRPIRankAndThreatTooltip(this.#taxon),
|
107
64
|
),
|
108
65
|
);
|
109
66
|
if (this.#taxon.getCESA()) {
|
@@ -126,7 +83,8 @@ export class PageTaxon extends GenericPage {
|
|
126
83
|
if (taxa.length > 1) {
|
127
84
|
for (const taxon of taxa) {
|
128
85
|
links.push(
|
129
|
-
|
86
|
+
HTMLTaxon.getHTMLLink(
|
87
|
+
taxon,
|
130
88
|
taxon.getName() !== this.#taxon.getName(),
|
131
89
|
),
|
132
90
|
);
|
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.23",
|
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": {
|
@@ -58,6 +58,7 @@
|
|
58
58
|
"eslint": "^9.18.0",
|
59
59
|
"jest": "^29.7.0",
|
60
60
|
"prettier": "^3.4.2",
|
61
|
+
"puppeteer": "^24.1.1",
|
61
62
|
"typescript": "^5.7.3"
|
62
63
|
}
|
63
64
|
}
|
package/scripts/build-ebook.js
CHANGED
@@ -5,7 +5,7 @@ import { ErrorLog } from "../lib/errorlog.js";
|
|
5
5
|
import { Files } from "../lib/files.js";
|
6
6
|
import { PlantBook } from "../lib/ebook/plantbook.js";
|
7
7
|
import { Program } from "../lib/program.js";
|
8
|
-
import { Taxa } from "../lib/taxa.js";
|
8
|
+
import { Taxa } from "../lib/taxonomy/taxa.js";
|
9
9
|
|
10
10
|
const program = Program.getProgram();
|
11
11
|
program
|
@@ -33,7 +33,7 @@ async function build(options) {
|
|
33
33
|
await buildBook(
|
34
34
|
outputBase + suffix,
|
35
35
|
path,
|
36
|
-
options.showFlowerErrors
|
36
|
+
options.showFlowerErrors,
|
37
37
|
);
|
38
38
|
}
|
39
39
|
}
|
@@ -42,7 +42,7 @@ async function build(options) {
|
|
42
42
|
await buildBook(
|
43
43
|
options.outputdir,
|
44
44
|
options.datadir,
|
45
|
-
options.showFlowerErrors
|
45
|
+
options.showFlowerErrors,
|
46
46
|
);
|
47
47
|
}
|
48
48
|
}
|
@@ -58,7 +58,7 @@ async function buildBook(outputDir, dataDir, showFlowerErrors) {
|
|
58
58
|
const taxa = new Taxa(
|
59
59
|
Program.getIncludeList(dataDir),
|
60
60
|
errorLog,
|
61
|
-
showFlowerErrors
|
61
|
+
showFlowerErrors,
|
62
62
|
);
|
63
63
|
|
64
64
|
const config = new Config(dataDir);
|