@ca-plant-list/ca-plant-list 0.4.6 → 0.4.9
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/.github/workflows/main.yml +20 -0
- package/.prettierrc +3 -1
- package/.vscode/settings.json +9 -0
- package/data/glossary/perianth.md +1 -0
- package/data/synonyms.csv +3 -1
- package/data/taxa.csv +7 -4
- package/data/text/Toxicoscordion-fremontii.md +1 -0
- package/data/text/Toxicoscordion-paniculatum.md +1 -0
- package/data/text/Toxicoscordion-venenosum-var-venenosum.md +1 -0
- package/ebook/css/main.css +9 -1
- package/eslint.config.mjs +9 -9
- package/lib/ebook/images.js +79 -52
- package/lib/ebook/pages/taxonpage.js +25 -27
- package/lib/ebook/plantbook.js +147 -139
- package/lib/htmltaxon.js +124 -117
- package/lib/inat_photo.js +15 -15
- package/lib/progressmeter.js +29 -0
- package/lib/web/pagetaxon.js +199 -190
- package/package.json +5 -3
- package/scripts/photos.js +68 -0
- package/types/classes.d.ts +14 -20
- package/data/photos.csv +0 -9
- package/lib/ebook/taxonimage.js +0 -23
@@ -0,0 +1,20 @@
|
|
1
|
+
on:
|
2
|
+
workflow_dispatch:
|
3
|
+
pull_request:
|
4
|
+
push:
|
5
|
+
|
6
|
+
permissions:
|
7
|
+
contents: read
|
8
|
+
|
9
|
+
jobs:
|
10
|
+
check:
|
11
|
+
runs-on: ubuntu-latest
|
12
|
+
steps:
|
13
|
+
- uses: actions/checkout@v4
|
14
|
+
- uses: actions/setup-node@v4
|
15
|
+
with:
|
16
|
+
node-version: latest
|
17
|
+
cache: "npm"
|
18
|
+
- run: npm update
|
19
|
+
- run: npx eslint
|
20
|
+
- run: npx tsc
|
package/.prettierrc
CHANGED
@@ -0,0 +1 @@
|
|
1
|
+
The collection of petals and [sepals](./sepal.html) on a flower.
|
package/data/synonyms.csv
CHANGED
@@ -59,6 +59,7 @@ Agrostis viridis,Polypogon viridis
|
|
59
59
|
Aira caryophyllea var. capillaris,Aira elegans
|
60
60
|
Aira elegantissima,Aira elegans
|
61
61
|
Aira pulchella,Aira elegans
|
62
|
+
Alchemilla occidentalis,Aphanes occidentalis,INAT
|
62
63
|
Allium acuminatum var. gracile,Allium amplectens
|
63
64
|
Allium attenuifolium var. monospermum,Allium amplectens
|
64
65
|
Allium attenuifolium,Allium amplectens
|
@@ -870,9 +871,9 @@ Helianthemum scoparium var. vulgare,Crocanthemum scoparium var. vulgare
|
|
870
871
|
Helianthus annuus subsp. jaegeri,Helianthus annuus
|
871
872
|
Helianthus annuus subsp. lenticularis,Helianthus annuus
|
872
873
|
Helianthus annuus var. macrocarpus,Helianthus annuus
|
873
|
-
Helosciadium nodiflorum,Apium nodiflorum,INAT
|
874
874
|
Heliotropium oculatum,Heliotropium curassavicum var. oculatum
|
875
875
|
Heliotropium spathulatum subsp. oculatum,Heliotropium curassavicum var. oculatum
|
876
|
+
Helosciadium nodiflorum,Apium nodiflorum,INAT
|
876
877
|
Helxine soleirolii,Soleirolia soleirolii
|
877
878
|
Hemizonia angustifolia,Deinandra corymbosa
|
878
879
|
Hemizonia corymbosa subsp. macrocephala,Deinandra corymbosa
|
@@ -1779,6 +1780,7 @@ Ruppia occidentalis,Ruppia cirrhosa
|
|
1779
1780
|
Ruppia spiralis,Ruppia cirrhosa
|
1780
1781
|
Sagina apetala var. barbata,Sagina apetala
|
1781
1782
|
Sagina occidentalis,Sagina decumbens subsp. occidentalis
|
1783
|
+
Sagittaria calycina,Sagittaria montevidensis subsp. calycina,INAT
|
1782
1784
|
Sairocarpus multiflorus,Antirrhinum thompsonii,INAT
|
1783
1785
|
Sairocarpus vexillocalyculatus subsp. vexillocalyculatus,Antirrhinum vexillocalyculatum subsp. vexillocalyculatum,INAT
|
1784
1786
|
Salicornia subterminalis,Arthrocnemum subterminale
|
package/data/taxa.csv
CHANGED
@@ -114,7 +114,7 @@ Antirrhinum kelloggii,,N,13568,401,165675
|
|
114
114
|
Antirrhinum majus,snapdragon,X,13557,404,48969
|
115
115
|
Antirrhinum thompsonii,Sierra snapdragon,N,108978,14286,168306,,pink,4,8
|
116
116
|
Antirrhinum vexillocalyculatum subsp. vexillocalyculatum,,N,88873,10499,840919,annual,purple,6,8
|
117
|
-
Aphanes occidentalis,western lady's mantle,N,13608
|
117
|
+
Aphanes occidentalis,western lady's mantle,N,13608,420,1452907,annual,,3,5
|
118
118
|
Aphyllon californicum subsp. jepsonii,,N,103325,13438,802459
|
119
119
|
Aphyllon epigalium subsp. epigalium,,N,103327,13527,809377
|
120
120
|
Aphyllon fasciculatum,clustered broom-rape,N,100018,13441,802543
|
@@ -1255,6 +1255,8 @@ Navarretia pubescens,downy pincushion,N,34471,5804,58976
|
|
1255
1255
|
Navarretia squarrosa,skunkweed,N,34475,5807,53157
|
1256
1256
|
Navarretia tagetina,,N,34477,5809,60226
|
1257
1257
|
Navarretia viscidula,sticky navarretia,N,34478,5810,78195
|
1258
|
+
Nemacladus capillaris,common threadplant,N,34503,5815,78199,annual,white,5,7
|
1259
|
+
Nemacladus montanus,mountain threadplant,N,34508,5824,78203,annual,white,5,7
|
1258
1260
|
Nemophila heterophylla,,N,34539,5834,53158,,white,2,6
|
1259
1261
|
Nemophila menziesii var. atomaria,,N,62279,5837,50647,,white,2,6
|
1260
1262
|
Nemophila menziesii var. menziesii,baby blue-eyes,N,62285,5839,50650
|
@@ -1582,6 +1584,7 @@ Sagina apetala,dwarf pearlwort,X,42572,7243,56998
|
|
1582
1584
|
Sagina decumbens subsp. occidentalis,western pearlwort,N,52717,7244,57000
|
1583
1585
|
Sagina procumbens,matted pearlwort,X,42584,7246,55667
|
1584
1586
|
Sagittaria latifolia,,N,42617,7249,48070
|
1587
|
+
Sagittaria montevidensis subsp. calycina,hooded arrowhead,N,52726,7251,1506723,annual,white,7,8
|
1585
1588
|
Salicornia depressa,glasswort,N,42663,10333,78930
|
1586
1589
|
Salicornia pacifica,pickleweed,N,42666,12001,78931
|
1587
1590
|
Salicornia rubra,,N,42661,7256,78933
|
@@ -1770,9 +1773,9 @@ Torilis arvensis,tall sock-destroyer,X,46743,8004,56846
|
|
1770
1773
|
Torilis nodosa,short sock-destroyer,X,46747,8007,53304
|
1771
1774
|
Torreyochloa pallida var. pauciflora,,N,67197,8010,81431
|
1772
1775
|
Toxicodendron diversilobum,poison-oak,N,46791,8015,51080,,white
|
1773
|
-
Toxicoscordion fremontii,zigadene,N,89163,11103,49649
|
1774
|
-
Toxicoscordion paniculatum,,N,46802,11105,79380
|
1775
|
-
Toxicoscordion venenosum var. venenosum,death-camas,N,93851,11106,81432
|
1776
|
+
Toxicoscordion fremontii,zigadene,N,89163,11103,49649,bulb,white,2,6
|
1777
|
+
Toxicoscordion paniculatum,,N,46802,11105,79380,bulb,white,5,6
|
1778
|
+
Toxicoscordion venenosum var. venenosum,death-camas,N,93851,11106,81432,bulb,white,5,7
|
1776
1779
|
Tradescantia fluminensis,,X,77137,8017,79382
|
1777
1780
|
Tragopogon porrifolius,salsify,X,5449,8020,54141
|
1778
1781
|
Trianthema portulacastrum,,N,46920,8023,79390
|
@@ -0,0 +1 @@
|
|
1
|
+
Leaves 8-30 mm wide. [Perianth](./perianth.html) parts 5-15 mm long.
|
@@ -0,0 +1 @@
|
|
1
|
+
Leaves 6-16 mm wide. [Perianth](./perianth.html) parts 2-6 mm long. Perianth parts unequal, outer parts generally without claws, inner parts with claws. Stamens at least as long as perianth parts.
|
@@ -0,0 +1 @@
|
|
1
|
+
Leaves 4-10 mm wide. [Perianth](./perianth.html) parts 4-6 mm long. Stamens at least as long as perianth parts.
|
package/ebook/css/main.css
CHANGED
@@ -12,6 +12,14 @@ div.section {
|
|
12
12
|
margin-bottom: .5rem;
|
13
13
|
}
|
14
14
|
|
15
|
+
div.photos {
|
16
|
+
display: flex;
|
17
|
+
}
|
18
|
+
|
19
|
+
figure {
|
20
|
+
margin: auto;
|
21
|
+
}
|
22
|
+
|
15
23
|
img.flr-color {
|
16
24
|
padding-right: .25rem;
|
17
25
|
width: 1rem;
|
@@ -29,4 +37,4 @@ span.lcs {
|
|
29
37
|
/* Glossary */
|
30
38
|
div.glossary img {
|
31
39
|
max-height: 300px;
|
32
|
-
}
|
40
|
+
}
|
package/eslint.config.mjs
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
+
import globals from "globals";
|
2
|
+
import pluginJs from "@eslint/js";
|
3
|
+
|
4
|
+
/** @type {import('eslint').Linter.Config[]} */
|
1
5
|
export default [
|
6
|
+
{ files: ["**/*.{js,mjs,cjs}"], ignores: ["output/**"] },
|
2
7
|
{
|
3
|
-
|
4
|
-
|
5
|
-
SwitchCase: 1,
|
6
|
-
}],
|
7
|
-
|
8
|
-
"linebreak-style": ["error", "unix"],
|
9
|
-
quotes: ["error", "double"],
|
10
|
-
semi: ["error", "always"],
|
8
|
+
languageOptions: {
|
9
|
+
globals: { ...globals.browser, ...globals.node, bootstrap: false },
|
11
10
|
},
|
12
|
-
}
|
11
|
+
},
|
12
|
+
pluginJs.configs.recommended,
|
13
13
|
];
|
package/lib/ebook/images.js
CHANGED
@@ -1,20 +1,15 @@
|
|
1
1
|
import * as fs from "node:fs";
|
2
|
-
import path from "node:path";
|
3
2
|
|
4
3
|
import sharp from "sharp";
|
5
4
|
|
6
5
|
import { EBook } from "./ebook.js";
|
7
|
-
import { Config } from "../config.js";
|
8
|
-
import { CSV } from "../csv.js";
|
9
6
|
import { Files } from "../files.js";
|
10
|
-
import {
|
7
|
+
import { ProgressMeter } from "../progressmeter.js";
|
11
8
|
|
12
9
|
class Images {
|
13
10
|
#siteGenerator;
|
14
11
|
#contentDir;
|
15
12
|
#taxa;
|
16
|
-
/** @type {Object<string,TaxonImage[]>} */
|
17
|
-
#images = {};
|
18
13
|
|
19
14
|
/**
|
20
15
|
* @param {SiteGenerator} siteGenerator
|
@@ -27,53 +22,63 @@ class Images {
|
|
27
22
|
this.#taxa = taxa;
|
28
23
|
}
|
29
24
|
|
30
|
-
|
25
|
+
/**
|
26
|
+
* @param {Taxon[]} taxa
|
27
|
+
*/
|
28
|
+
async createImages(taxa) {
|
29
|
+
const meter = new ProgressMeter("processing photos", taxa.length);
|
30
|
+
|
31
|
+
const width = 300;
|
32
|
+
const quality = 40;
|
33
|
+
|
31
34
|
const photoDirSrc = "external_data/photos";
|
32
|
-
const
|
33
|
-
|
34
|
-
|
35
|
-
fs.mkdirSync(photoDirTarget, { recursive: true });
|
36
|
-
|
37
|
-
const rows = CSV.parseFile(
|
38
|
-
Config.getPackageDir() + "/data",
|
39
|
-
"photos.csv"
|
35
|
+
const photoDirCache = `external_data/photos-${width}-${quality}`;
|
36
|
+
[photoDirSrc, photoDirCache, this.#contentDir + "/i"].forEach((dir) =>
|
37
|
+
fs.mkdirSync(dir, { recursive: true }),
|
40
38
|
);
|
41
|
-
for (const row of rows) {
|
42
|
-
const name = row["taxon_name"];
|
43
|
-
const taxon = this.#taxa.getTaxon(name);
|
44
|
-
if (!taxon) {
|
45
|
-
continue;
|
46
|
-
}
|
47
39
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
40
|
+
let downloadCount = 0;
|
41
|
+
let compressCount = 0;
|
42
|
+
let copyCount = 0;
|
43
|
+
|
44
|
+
for (let index = 0; index < taxa.length; index++) {
|
45
|
+
const taxon = taxa[index];
|
46
|
+
const photos = Images.getTaxonPhotos(taxon);
|
47
|
+
for (const photo of photos) {
|
48
|
+
const ext = photo.getExt();
|
49
|
+
const cachedFileName = `${photoDirCache}/${this.getCompressedImageName(photo)}`;
|
50
|
+
|
51
|
+
if (!fs.existsSync(cachedFileName)) {
|
52
|
+
const srcFileName = `${photoDirSrc}/${photo.getId()}.${ext}`;
|
53
|
+
// Compress and cache source file.
|
54
|
+
if (!fs.existsSync(srcFileName)) {
|
55
|
+
// Retrieve original file.
|
56
|
+
if (!fs.existsSync(srcFileName)) {
|
57
|
+
// File is not there; retrieve it.
|
58
|
+
await Files.fetch(photo.getUrl(), srcFileName);
|
59
|
+
downloadCount++;
|
60
|
+
}
|
61
|
+
}
|
62
|
+
await sharp(srcFileName)
|
63
|
+
.resize({ width: width })
|
64
|
+
.jpeg({ quality: quality })
|
65
|
+
.toFile(cachedFileName);
|
66
|
+
compressCount++;
|
67
|
+
}
|
68
|
+
|
69
|
+
fs.copyFileSync(
|
70
|
+
cachedFileName,
|
71
|
+
this.getCompressedFilePath(photo),
|
72
|
+
);
|
73
|
+
copyCount++;
|
52
74
|
}
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
const prefix = src.host.includes("calflora") ? "cf-" : "inat-";
|
57
|
-
const filename = prefix + parts.slice(-1)[0] + ".jpg";
|
58
|
-
const srcFileName = photoDirSrc + "/" + filename;
|
59
|
-
const targetFileName = photoDirTarget + "/" + filename;
|
60
|
-
|
61
|
-
if (!fs.existsSync(srcFileName)) {
|
62
|
-
// File is not there; retrieve it.
|
63
|
-
console.log("retrieving " + srcFileName);
|
64
|
-
await Files.fetch(src, srcFileName);
|
65
|
-
}
|
66
|
-
|
67
|
-
await sharp(srcFileName)
|
68
|
-
.resize({ width: 300 })
|
69
|
-
.jpeg({ quality: 40 })
|
70
|
-
.toFile(targetFileName);
|
71
|
-
|
72
|
-
imageList.push(
|
73
|
-
new TaxonImage(imagePrefix + "/" + filename, row["credit"])
|
74
|
-
);
|
75
|
+
meter.update(index + 1, {
|
76
|
+
custom: ` | ${copyCount} copied - ${compressCount} compressed - ${downloadCount} downloaded`,
|
77
|
+
});
|
75
78
|
}
|
76
79
|
|
80
|
+
meter.stop();
|
81
|
+
|
77
82
|
this.#siteGenerator.copyIllustrations(this.#taxa.getFlowerColors());
|
78
83
|
}
|
79
84
|
|
@@ -88,8 +93,8 @@ class Images {
|
|
88
93
|
EBook.getManifestEntry(
|
89
94
|
"i" + index,
|
90
95
|
"i/" + fileName,
|
91
|
-
EBook.getMediaTypeForExt(ext)
|
92
|
-
)
|
96
|
+
EBook.getMediaTypeForExt(ext),
|
97
|
+
),
|
93
98
|
);
|
94
99
|
}
|
95
100
|
|
@@ -97,10 +102,32 @@ class Images {
|
|
97
102
|
}
|
98
103
|
|
99
104
|
/**
|
100
|
-
* @param {
|
105
|
+
* @param {Photo} photo
|
106
|
+
* @returns {string}
|
107
|
+
*/
|
108
|
+
getCompressedFilePath(photo) {
|
109
|
+
return `${this.#contentDir}/i/${this.getCompressedImageName(photo)}`;
|
110
|
+
}
|
111
|
+
|
112
|
+
/**
|
113
|
+
* @param {Photo} photo
|
114
|
+
* @returns {string}
|
101
115
|
*/
|
102
|
-
|
103
|
-
return
|
116
|
+
getCompressedImageName(photo) {
|
117
|
+
return `${photo.getId()}.${photo.getExt().toLowerCase()}`;
|
118
|
+
}
|
119
|
+
|
120
|
+
/**
|
121
|
+
* @param {Taxon} taxon
|
122
|
+
* @returns {Photo[]}
|
123
|
+
*/
|
124
|
+
static getTaxonPhotos(taxon) {
|
125
|
+
const photos = taxon
|
126
|
+
.getPhotos()
|
127
|
+
.filter((photo) =>
|
128
|
+
["jpg", "jpeg"].includes(photo.getExt().toLowerCase()),
|
129
|
+
);
|
130
|
+
return photos.length > 0 ? [photos[0]] : photos;
|
104
131
|
}
|
105
132
|
}
|
106
133
|
|
@@ -1,26 +1,25 @@
|
|
1
|
-
import imageSize from "image-size";
|
2
1
|
import { EBookPage } from "../ebookpage.js";
|
3
2
|
import { XHTML } from "../xhtml.js";
|
4
3
|
import { Markdown } from "../../markdown.js";
|
5
4
|
import { HTMLTaxon } from "../../htmltaxon.js";
|
6
5
|
import { Config } from "../../config.js";
|
7
6
|
import { Files } from "../../files.js";
|
7
|
+
import { Images } from "../images.js";
|
8
|
+
import imageSize from "image-size";
|
8
9
|
|
9
10
|
class TaxonPage extends EBookPage {
|
10
|
-
#outputDir;
|
11
11
|
#taxon;
|
12
|
-
#
|
12
|
+
#images;
|
13
13
|
|
14
14
|
/**
|
15
15
|
* @param {string} outputDir
|
16
16
|
* @param {Taxon} taxon
|
17
|
-
* @param {
|
17
|
+
* @param {Images} images
|
18
18
|
*/
|
19
|
-
constructor(outputDir, taxon,
|
19
|
+
constructor(outputDir, taxon, images) {
|
20
20
|
super(outputDir + "/" + taxon.getFileName(), taxon.getName());
|
21
|
-
this.#outputDir = outputDir;
|
22
21
|
this.#taxon = taxon;
|
23
|
-
this.#
|
22
|
+
this.#images = images;
|
24
23
|
}
|
25
24
|
|
26
25
|
renderPageBody() {
|
@@ -45,7 +44,7 @@ class TaxonPage extends EBookPage {
|
|
45
44
|
html += XHTML.wrap(
|
46
45
|
"div",
|
47
46
|
XHTML.getLink(family.getFileName(), family.getName()),
|
48
|
-
{ class: "section" }
|
47
|
+
{ class: "section" },
|
49
48
|
);
|
50
49
|
|
51
50
|
const cn = this.#taxon.getCommonNames();
|
@@ -59,26 +58,25 @@ class TaxonPage extends EBookPage {
|
|
59
58
|
|
60
59
|
html += renderCustomText(this.#taxon.getBaseFileName());
|
61
60
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
}
|
77
|
-
photoHTML += XHTML.wrap("figure", img);
|
78
|
-
}
|
79
|
-
if (photoHTML) {
|
80
|
-
html += XHTML.wrap("div", photoHTML);
|
61
|
+
const photos = Images.getTaxonPhotos(this.#taxon);
|
62
|
+
|
63
|
+
let photoHTML = "";
|
64
|
+
for (const photo of photos) {
|
65
|
+
const dimensions = imageSize.imageSize(
|
66
|
+
this.#images.getCompressedFilePath(photo),
|
67
|
+
);
|
68
|
+
let img = XHTML.textElement("img", "", {
|
69
|
+
src: `i/${this.#images.getCompressedImageName(photo)}`,
|
70
|
+
style: "max-width:" + dimensions.width + "px",
|
71
|
+
});
|
72
|
+
const caption = `${photo.rights === "CC0" ? "By" : "(c)"} ${photo.rightsHolder} ${photo.rights && `(${photo.rights})`}`;
|
73
|
+
if (caption) {
|
74
|
+
img += XHTML.textElement("figcaption", caption);
|
81
75
|
}
|
76
|
+
photoHTML += XHTML.wrap("figure", img);
|
77
|
+
}
|
78
|
+
if (photoHTML) {
|
79
|
+
html += XHTML.wrap("div", photoHTML, "photos");
|
82
80
|
}
|
83
81
|
|
84
82
|
return html;
|