@ca-plant-list/ca-plant-list 0.4.33 → 0.4.35
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/inatobsphotos.csv +439 -66
- package/data/inattaxonphotos.csv +518 -134
- package/data/synonyms.csv +80 -0
- package/data/taxa.csv +1976 -1905
- package/data/text/Allium-obtusum-var-obtusum.md +1 -0
- package/data/text/Allium-validum.md +1 -0
- package/data/text/Amelanchier-alnifolia-var-pumila.md +1 -0
- package/data/text/Amelanchier-utahensis.md +1 -0
- package/data/text/Angelica-breweri.md +1 -0
- package/data/text/Angelica-capitellata.md +1 -0
- package/data/text/Castilleja-applegatei-subsp-pallida.md +1 -0
- package/data/text/Castilleja-miniata-subsp-miniata.md +1 -0
- package/data/text/Ceanothus-cordulatus.md +1 -0
- package/data/text/Ceanothus-velutinus.md +1 -0
- package/data/text/Collinsia-parviflora.md +1 -0
- package/data/text/Collinsia-torreyi.md +1 -0
- package/data/text/Ericameria-nauseosa-var-speciosa.md +1 -0
- package/data/text/Ericameria-nauseosa.md +1 -0
- package/data/text/Erigeron-algidus.md +1 -0
- package/data/text/Erigeron-coulteri.md +1 -0
- package/data/text/Hackelia-micrantha.md +1 -0
- package/data/text/Linanthus-pungens-subsp-pulchriflorus.md +1 -0
- package/data/text/Lupinus-argenteus-var-meionanthus.md +1 -0
- package/data/text/Lupinus-latifolius-var-latifolius.md +1 -1
- package/data/text/Lupinus-polyphyllus-var-burkei.md +1 -0
- package/data/text/Penstemon-deustus-var-deustus.md +1 -0
- package/data/text/Penstemon-deustus-var-pedicellatus.md +1 -0
- package/data/text/Penstemon-heterodoxus-var-heterodoxus.md +1 -0
- package/data/text/Penstemon-rydbergii-var-oreocharis.md +1 -0
- package/data/text/Phlox-diffusa.md +1 -0
- package/data/text/Potentilla-flabellifolia.md +1 -0
- package/data/text/Potentilla-gracilis-var-fastigiata.md +1 -0
- package/data/text/Primula-jeffreyi.md +1 -1
- package/data/text/Primula-tetrandra.md +1 -1
- package/data/text/Pyrola-asarifolia-subsp-asarifolia.md +1 -0
- package/data/text/Pyrola-dentata.md +1 -0
- package/data/text/Pyrola-picta.md +1 -0
- package/data/text/Salix-lemmonii.md +1 -0
- package/data/text/Salix-scouleriana.md +1 -0
- package/data/text/Senecio-integerrimus-var-exaltatus.md +1 -0
- package/data/text/Senecio-integerrimus-var-major.md +1 -0
- package/data/text/Senecio-triangularis.md +1 -0
- package/data/text/Sorbus-californica.md +1 -0
- package/data/text/Sorbus-scopulina.md +1 -0
- package/ebook/css/main.css +8 -0
- package/lib/ebook/ebook.js +178 -178
- package/lib/ebook/pages/taxonpage.js +19 -9
- package/lib/ebook/plantbook.js +17 -3
- package/lib/externalsites.js +12 -0
- package/lib/htmltaxon.js +4 -0
- package/lib/index.d.ts +2 -0
- package/lib/taxonomy/taxon.js +12 -2
- package/lib/tools/calflora.js +0 -2
- package/lib/tools/calipc.js +111 -0
- package/lib/tools/taxacsv.js +5 -4
- package/lib/utils/eleventyGenerator.js +2 -0
- package/lib/utils/inat-tools.js +8 -4
- package/lib/web/pageTaxon.js +1 -0
- package/package.json +1 -1
- package/scripts/build-ebook.js +20 -5
- package/scripts/cpl-tools.js +15 -1
package/lib/externalsites.js
CHANGED
@@ -30,6 +30,18 @@ export class ExternalSites {
|
|
30
30
|
return new URL("https://www.calflora.org/app/taxon?crn=" + calfloraID);
|
31
31
|
}
|
32
32
|
|
33
|
+
/**
|
34
|
+
* @param {import("./types.js").Taxon} taxon
|
35
|
+
* @returns {URL|undefined}
|
36
|
+
*/
|
37
|
+
static getCalIPCRefLink(taxon) {
|
38
|
+
const calipcID = taxon.getCalIPCID();
|
39
|
+
if (!calipcID) {
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
return new URL(`https://www.cal-ipc.org/plants/profile/${calipcID}/`);
|
43
|
+
}
|
44
|
+
|
33
45
|
/**
|
34
46
|
* @param {import("./types.js").Taxon} taxon
|
35
47
|
* @returns {URL|undefined}
|
package/lib/htmltaxon.js
CHANGED
@@ -74,6 +74,10 @@ const REFLINKS = {
|
|
74
74
|
label: "Calflora",
|
75
75
|
href: (taxon) => ExternalSites.getCalfloraRefLink(taxon),
|
76
76
|
},
|
77
|
+
calipc: {
|
78
|
+
label: "Cal-IPC",
|
79
|
+
href: (taxon) => ExternalSites.getCalIPCRefLink(taxon),
|
80
|
+
},
|
77
81
|
calscape: {
|
78
82
|
label: "Calscape",
|
79
83
|
href: (taxon) => ExternalSites.getCalscapeLink(taxon),
|
package/lib/index.d.ts
CHANGED
@@ -8,6 +8,7 @@ type PhotoRights = "CC0" | "CC BY" | "CC BY-NC" | "C" | null;
|
|
8
8
|
|
9
9
|
type RefSourceCode =
|
10
10
|
| "calflora"
|
11
|
+
| "calipc"
|
11
12
|
| "calscape"
|
12
13
|
| "cch"
|
13
14
|
| "fna"
|
@@ -40,6 +41,7 @@ export type TaxonData = TaxonomyData & {
|
|
40
41
|
CRPR: string;
|
41
42
|
FESA: string;
|
42
43
|
fna: string;
|
44
|
+
calipc: string;
|
43
45
|
flower_color: string;
|
44
46
|
GRank: string;
|
45
47
|
"inat id": string;
|
package/lib/taxonomy/taxon.js
CHANGED
@@ -13,6 +13,7 @@ class Taxon extends Taxonomy {
|
|
13
13
|
#cch2id;
|
14
14
|
#fnaName;
|
15
15
|
#calscapeCN;
|
16
|
+
#calipcID;
|
16
17
|
#lifeCycle;
|
17
18
|
#flowerColors;
|
18
19
|
#bloomStart;
|
@@ -51,6 +52,7 @@ class Taxon extends Taxonomy {
|
|
51
52
|
this.#iNatID = data["inat id"];
|
52
53
|
this.#cch2id = data.cch2_id;
|
53
54
|
this.#fnaName = data.fna ?? "";
|
55
|
+
this.#calipcID = data.calipc ?? "";
|
54
56
|
this.#calscapeCN =
|
55
57
|
data.calscape_cn === "" ? undefined : data.calscape_cn;
|
56
58
|
this.#lifeCycle = data.life_cycle;
|
@@ -118,12 +120,20 @@ class Taxon extends Taxonomy {
|
|
118
120
|
return this.#bloomStart;
|
119
121
|
}
|
120
122
|
|
123
|
+
getCalfloraID() {
|
124
|
+
return this.#calRecNum;
|
125
|
+
}
|
126
|
+
|
121
127
|
getCalfloraName() {
|
122
128
|
return this.getName().replace(" subsp.", " ssp.").replace("×", "X");
|
123
129
|
}
|
124
130
|
|
125
|
-
|
126
|
-
return this.#
|
131
|
+
getCalIPCID() {
|
132
|
+
return this.#calipcID;
|
133
|
+
}
|
134
|
+
|
135
|
+
getCalIPCName() {
|
136
|
+
return this.getName().replace(" subsp.", " ssp.");
|
127
137
|
}
|
128
138
|
|
129
139
|
getCalscapeCommonName() {
|
package/lib/tools/calflora.js
CHANGED
@@ -70,12 +70,10 @@ export class Calflora {
|
|
70
70
|
);
|
71
71
|
|
72
72
|
/** @type {CalfloraData[]} */
|
73
|
-
// @ts-ignore
|
74
73
|
const csvActive = CSV.readFile(
|
75
74
|
path.join(toolsDataPath, calfloraDataFileNameActive),
|
76
75
|
);
|
77
76
|
/** @type {CalfloraData[]} */
|
78
|
-
// @ts-ignore
|
79
77
|
const csvCounties = CSV.readFile(
|
80
78
|
path.join(toolsDataPath, calfloraDataFileNameCounties),
|
81
79
|
);
|
@@ -0,0 +1,111 @@
|
|
1
|
+
import * as path from "path";
|
2
|
+
import { Files } from "../files.js";
|
3
|
+
import { TaxaCSV } from "./taxacsv.js";
|
4
|
+
import { scrape } from "@htmltools/scrape";
|
5
|
+
|
6
|
+
export class CalIPC {
|
7
|
+
/**
|
8
|
+
* @param {string} toolsDataDir
|
9
|
+
* @param {string} dataDir
|
10
|
+
* @param {import("../types.js").Taxa} taxa
|
11
|
+
* @param {import("../exceptions.js").Exceptions} exceptions
|
12
|
+
* @param {import("../errorlog.js").ErrorLog} errorLog
|
13
|
+
* @param {boolean} update
|
14
|
+
*/
|
15
|
+
static async analyze(
|
16
|
+
toolsDataDir,
|
17
|
+
dataDir,
|
18
|
+
taxa,
|
19
|
+
exceptions,
|
20
|
+
errorLog,
|
21
|
+
update,
|
22
|
+
) {
|
23
|
+
const toolsDataPath = toolsDataDir + "/calipc";
|
24
|
+
// Create data directory if it's not there.
|
25
|
+
Files.mkdir(toolsDataPath);
|
26
|
+
|
27
|
+
const calipcFileName = path.join(toolsDataPath, "inventory.html");
|
28
|
+
if (!Files.exists(calipcFileName)) {
|
29
|
+
console.info("retrieving " + calipcFileName);
|
30
|
+
await Files.fetch(
|
31
|
+
"https://www.cal-ipc.org/plants/inventory/",
|
32
|
+
calipcFileName,
|
33
|
+
);
|
34
|
+
}
|
35
|
+
|
36
|
+
const parsed = scrape.parseFile(calipcFileName);
|
37
|
+
const links = scrape.getSubtrees(parsed, (e) => {
|
38
|
+
if (e.tagName !== "td") {
|
39
|
+
return false;
|
40
|
+
}
|
41
|
+
const className = scrape.getAttr(e, "class");
|
42
|
+
return className === "it-latin";
|
43
|
+
});
|
44
|
+
|
45
|
+
/** @type {Map<string,string>} */
|
46
|
+
const calipcTaxa = new Map();
|
47
|
+
for (const link of links) {
|
48
|
+
const name = scrape.getTextContent(link);
|
49
|
+
const url = scrape.getAttr(link.children[0], "href");
|
50
|
+
if (!url) {
|
51
|
+
console.warn(`Cal-IPC url not found for ${name}`);
|
52
|
+
continue;
|
53
|
+
}
|
54
|
+
const id = url.match(/\/profile\/(.*)\//);
|
55
|
+
if (!id) {
|
56
|
+
console.warn(`Cal-IPC url mismatch for ${url}`);
|
57
|
+
continue;
|
58
|
+
}
|
59
|
+
calipcTaxa.set(name, id[1]);
|
60
|
+
}
|
61
|
+
|
62
|
+
const idsToUpdate = new Map();
|
63
|
+
|
64
|
+
for (const taxon of taxa.getTaxonList()) {
|
65
|
+
const name = taxon.getName();
|
66
|
+
if (name.includes(" unknown")) {
|
67
|
+
continue;
|
68
|
+
}
|
69
|
+
const calipcName = taxon.getCalIPCName();
|
70
|
+
const calipcData = calipcTaxa.get(calipcName);
|
71
|
+
if (!calipcData) {
|
72
|
+
continue;
|
73
|
+
}
|
74
|
+
|
75
|
+
// Check native status.
|
76
|
+
if (taxon.isCANative()) {
|
77
|
+
errorLog.log(name, "is native but listed in Cal-IPC");
|
78
|
+
}
|
79
|
+
|
80
|
+
if (calipcData !== taxon.getCalIPCID()) {
|
81
|
+
errorLog.log(
|
82
|
+
name,
|
83
|
+
"Cal-IPC ID in Cal-IPC is different than taxa.csv",
|
84
|
+
calipcData,
|
85
|
+
taxon.getCalIPCID(),
|
86
|
+
);
|
87
|
+
idsToUpdate.set(name, calipcData);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
if (update) {
|
92
|
+
this.#updateIds(dataDir, idsToUpdate);
|
93
|
+
}
|
94
|
+
}
|
95
|
+
|
96
|
+
/**
|
97
|
+
* @param {string} dataDir
|
98
|
+
* @param {Map<string,string>} idsToUpdate
|
99
|
+
*/
|
100
|
+
static #updateIds(dataDir, idsToUpdate) {
|
101
|
+
const taxa = new TaxaCSV(dataDir);
|
102
|
+
for (const taxonData of taxa.getTaxa()) {
|
103
|
+
const id = idsToUpdate.get(taxonData.taxon_name);
|
104
|
+
if (!id) {
|
105
|
+
continue;
|
106
|
+
}
|
107
|
+
taxonData["calipc"] = id;
|
108
|
+
}
|
109
|
+
taxa.write();
|
110
|
+
}
|
111
|
+
}
|
package/lib/tools/taxacsv.js
CHANGED
@@ -5,16 +5,17 @@ const HEADERS = [
|
|
5
5
|
"taxon_name",
|
6
6
|
"common name",
|
7
7
|
"status",
|
8
|
+
"life_cycle",
|
9
|
+
"flower_color",
|
10
|
+
"bloom_start",
|
11
|
+
"bloom_end",
|
8
12
|
"jepson id",
|
9
13
|
"calrecnum",
|
10
14
|
"inat id",
|
11
15
|
"cch2_id",
|
12
16
|
"fna",
|
13
17
|
"calscape_cn",
|
14
|
-
"
|
15
|
-
"flower_color",
|
16
|
-
"bloom_start",
|
17
|
-
"bloom_end",
|
18
|
+
"calipc",
|
18
19
|
"RPI ID",
|
19
20
|
"CRPR",
|
20
21
|
"CESA",
|
package/lib/utils/inat-tools.js
CHANGED
@@ -69,7 +69,11 @@ export function convertToCSVPhoto(apiPhoto) {
|
|
69
69
|
*/
|
70
70
|
async function fetchInatTaxa(inatTaxonIDs) {
|
71
71
|
const url = `https://api.inaturalist.org/v2/taxa/${inatTaxonIDs.join(",")}?fields=(taxon_photos:(photo:(medium_url:!t,attribution:!t,license_code:!t)))`;
|
72
|
-
const resp = await
|
72
|
+
const resp = await getResponse(url);
|
73
|
+
if (resp instanceof Error) {
|
74
|
+
console.error(`unable to fetch taxa: ${resp.message}`);
|
75
|
+
return [];
|
76
|
+
}
|
73
77
|
if (!resp.ok) {
|
74
78
|
const error = await resp.text();
|
75
79
|
throw new Error(`Failed to fetch taxa from iNat: ${error}`);
|
@@ -163,7 +167,7 @@ export async function getObsPhotosForTaxa(taxaToUpdate, locationOptions) {
|
|
163
167
|
continue;
|
164
168
|
}
|
165
169
|
|
166
|
-
// Just get the CC-licensed ones, 5 per taxon should be fine (max is 20 on iNat).
|
170
|
+
// Just get the CC-licensed ones, 5 per taxon should be fine (max is 20 on iNat).
|
167
171
|
const rawPhotoInfo = observations
|
168
172
|
.map((obs) =>
|
169
173
|
obs.observation_photos.map((op) => {
|
@@ -182,7 +186,7 @@ export async function getObsPhotosForTaxa(taxaToUpdate, locationOptions) {
|
|
182
186
|
if (!obj) {
|
183
187
|
continue;
|
184
188
|
}
|
185
|
-
processedPhotoInfo.push(obj);
|
189
|
+
processedPhotoInfo.push({ obsId: photo.obsId.toString(), ...obj });
|
186
190
|
}
|
187
191
|
photos.set(taxon.getName(), processedPhotoInfo);
|
188
192
|
|
@@ -270,7 +274,7 @@ function getAttribution(rawAttribution) {
|
|
270
274
|
|
271
275
|
let lastQueryTime = Date.now();
|
272
276
|
/**
|
273
|
-
* @param {URL} url
|
277
|
+
* @param {URL|string} url
|
274
278
|
* @returns {Promise<Response|Error>}
|
275
279
|
*/
|
276
280
|
async function getResponse(url) {
|
package/lib/web/pageTaxon.js
CHANGED
@@ -35,6 +35,7 @@ export class PageTaxon extends GenericPage {
|
|
35
35
|
HTMLTaxon.addRefLink(links, this.#taxon, "fna");
|
36
36
|
HTMLTaxon.addRefLink(links, this.#taxon, "cch");
|
37
37
|
HTMLTaxon.addRefLink(links, this.#taxon, "calscape");
|
38
|
+
HTMLTaxon.addRefLink(links, this.#taxon, "calipc");
|
38
39
|
return links;
|
39
40
|
}
|
40
41
|
|
package/package.json
CHANGED
package/scripts/build-ebook.js
CHANGED
@@ -8,9 +8,16 @@ import { Program } from "../lib/program.js";
|
|
8
8
|
import { Taxa } from "../lib/taxonomy/taxa.js";
|
9
9
|
|
10
10
|
const program = Program.getProgram();
|
11
|
-
program
|
12
|
-
|
13
|
-
|
11
|
+
program.option(
|
12
|
+
"-l, --locationsdir <dir>",
|
13
|
+
"directory containing location data",
|
14
|
+
);
|
15
|
+
program.option(
|
16
|
+
"--location <name>",
|
17
|
+
"name of location to generate (otherwise all ebooks will be generated)",
|
18
|
+
);
|
19
|
+
program.option("--max-taxa <number>", "maximum number of taxa to include");
|
20
|
+
program.action(build);
|
14
21
|
|
15
22
|
await program.parseAsync();
|
16
23
|
|
@@ -26,6 +33,11 @@ async function build(options) {
|
|
26
33
|
const outputBase = options.outputdir;
|
27
34
|
const subdirs = Files.getDirEntries(locationsDir);
|
28
35
|
for (const subdir of subdirs) {
|
36
|
+
// If a single location was specified, ignore the others.
|
37
|
+
if (options.location && subdir !== options.location) {
|
38
|
+
continue;
|
39
|
+
}
|
40
|
+
|
29
41
|
console.log("Generating " + subdir);
|
30
42
|
const suffix = "/" + subdir;
|
31
43
|
const path = locationsDir + suffix;
|
@@ -34,6 +46,7 @@ async function build(options) {
|
|
34
46
|
outputBase + suffix,
|
35
47
|
path,
|
36
48
|
options.showFlowerErrors,
|
49
|
+
options.maxTaxa,
|
37
50
|
);
|
38
51
|
}
|
39
52
|
}
|
@@ -43,6 +56,7 @@ async function build(options) {
|
|
43
56
|
options.outputdir,
|
44
57
|
options.datadir,
|
45
58
|
options.showFlowerErrors,
|
59
|
+
options.maxTaxa,
|
46
60
|
);
|
47
61
|
}
|
48
62
|
}
|
@@ -51,8 +65,9 @@ async function build(options) {
|
|
51
65
|
* @param {string} outputDir
|
52
66
|
* @param {string} dataDir
|
53
67
|
* @param {boolean} showFlowerErrors
|
68
|
+
* @param {number|undefined} maxTaxa
|
54
69
|
*/
|
55
|
-
async function buildBook(outputDir, dataDir, showFlowerErrors) {
|
70
|
+
async function buildBook(outputDir, dataDir, showFlowerErrors, maxTaxa) {
|
56
71
|
const errorLog = new ErrorLog(outputDir + "/errors.tsv");
|
57
72
|
|
58
73
|
const taxa = new Taxa(
|
@@ -62,7 +77,7 @@ async function buildBook(outputDir, dataDir, showFlowerErrors) {
|
|
62
77
|
);
|
63
78
|
|
64
79
|
const config = new Config(dataDir);
|
65
|
-
const ebook = new PlantBook(outputDir, config, taxa);
|
80
|
+
const ebook = new PlantBook(outputDir, config, taxa, maxTaxa);
|
66
81
|
await ebook.create();
|
67
82
|
errorLog.write();
|
68
83
|
}
|
package/scripts/cpl-tools.js
CHANGED
@@ -15,9 +15,11 @@ import { SupplementalText } from "../lib/tools/supplementaltext.js";
|
|
15
15
|
import { JepsonFamilies } from "../lib/tools/jepsonfamilies.js";
|
16
16
|
import { CCH2 } from "../lib/tools/cch2.js";
|
17
17
|
import { FNA } from "../lib/tools/fna.js";
|
18
|
+
import { CalIPC } from "../lib/tools/calipc.js";
|
18
19
|
|
19
20
|
const TOOLS = {
|
20
21
|
CALFLORA: "calflora",
|
22
|
+
CAL_IPC: "calipc",
|
21
23
|
CALSCAPE: "calscape",
|
22
24
|
CCH2: "cch",
|
23
25
|
FNA: "fna",
|
@@ -30,6 +32,7 @@ const TOOLS = {
|
|
30
32
|
|
31
33
|
const ALL_TOOLS = [
|
32
34
|
TOOLS.CALFLORA,
|
35
|
+
TOOLS.CAL_IPC,
|
33
36
|
TOOLS.CALSCAPE,
|
34
37
|
TOOLS.CCH2,
|
35
38
|
TOOLS.FNA,
|
@@ -76,6 +79,16 @@ async function build(program, options) {
|
|
76
79
|
!!options.update,
|
77
80
|
);
|
78
81
|
break;
|
82
|
+
case TOOLS.CAL_IPC:
|
83
|
+
await CalIPC.analyze(
|
84
|
+
TOOLS_DATA_DIR,
|
85
|
+
options.datadir,
|
86
|
+
taxa,
|
87
|
+
exceptions,
|
88
|
+
errorLog,
|
89
|
+
!!options.update,
|
90
|
+
);
|
91
|
+
break;
|
79
92
|
case TOOLS.CALSCAPE:
|
80
93
|
await Calscape.analyze(
|
81
94
|
TOOLS_DATA_DIR,
|
@@ -167,8 +180,9 @@ program.addHelpText(
|
|
167
180
|
"after",
|
168
181
|
`
|
169
182
|
Tools:
|
170
|
-
'all' runs the 'calflora', '${TOOLS.CALSCAPE}', '${TOOLS.CCH2}, '${TOOLS.FNA}, 'inat', 'jepson-eflora', 'rpi', and 'text' tools.
|
183
|
+
'all' runs the 'calflora', '${TOOLS.CAL_IPC}', '${TOOLS.CALSCAPE}', '${TOOLS.CCH2}, '${TOOLS.FNA}, 'inat', 'jepson-eflora', 'rpi', and 'text' tools.
|
171
184
|
'${TOOLS.CALFLORA}' retrieves data from Calflora and compares with local data.
|
185
|
+
'${TOOLS.CAL_IPC}' retrieves data from Cal-IPC and compares with local data.
|
172
186
|
'${TOOLS.CALSCAPE}' retrieves data from Calscape and compares with local data.
|
173
187
|
'${TOOLS.CCH2}' retrieves data from CCH2 and compares with local data.
|
174
188
|
'${TOOLS.FNA}' retrieves data from Flora of North America and compares with local data.
|