@ca-plant-list/ca-plant-list 0.4.9 → 0.4.10

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.
@@ -0,0 +1,179 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from "path";
4
+ import { ErrorLog } from "../lib/errorlog.js";
5
+ import { Program } from "../lib/program.js";
6
+ import { Taxa } from "../lib/taxa.js";
7
+ import { getTaxonPhotos } from "../lib/utils/inat-tools.js";
8
+ import { existsSync } from "fs";
9
+ import { CSV } from "../lib/csv.js";
10
+
11
+ const PHOTO_FILE_NAME = "inattaxonphotos.csv";
12
+
13
+ const OPT_LOADER = "loader";
14
+
15
+ const MAX_PHOTOS = 5;
16
+
17
+ /**
18
+ * @param {import("commander").OptionValues} options
19
+ */
20
+ async function addMissingPhotos(options) {
21
+ const taxaMissingPhotos = [];
22
+
23
+ const taxa = await getTaxa(options);
24
+ const errorLog = new ErrorLog(options.outputdir + "/log.tsv", true);
25
+
26
+ for (const taxon of taxa.getTaxonList()) {
27
+ const photos = taxon.getPhotos();
28
+ if (photos.length < MAX_PHOTOS) {
29
+ taxaMissingPhotos.push(taxon);
30
+ }
31
+ }
32
+
33
+ const newPhotos = await getTaxonPhotos(taxaMissingPhotos);
34
+ const currentTaxaPhotos = readPhotos();
35
+
36
+ for (const [taxonName, photos] of newPhotos) {
37
+ let currentPhotos = currentTaxaPhotos.get(taxonName);
38
+ if (!currentPhotos) {
39
+ currentPhotos = [];
40
+ currentTaxaPhotos.set(taxonName, currentPhotos);
41
+ }
42
+ for (const photo of photos) {
43
+ if (currentPhotos.length === MAX_PHOTOS) {
44
+ break;
45
+ }
46
+ if (
47
+ currentPhotos.some(
48
+ (currentPhoto) => currentPhoto.id === photo.id,
49
+ )
50
+ ) {
51
+ continue;
52
+ }
53
+ currentPhotos.push(photo);
54
+ errorLog.log("adding photo", taxonName, photo.id);
55
+ }
56
+ }
57
+
58
+ errorLog.write();
59
+
60
+ // Write updated photo file.
61
+ const headers = ["name", "id", "ext", "licenseCode", "attrName"];
62
+ /** @type {string[][]} */
63
+ const data = [];
64
+ for (const taxonName of [...currentTaxaPhotos.keys()].sort()) {
65
+ // @ts-ignore
66
+ for (const photo of currentTaxaPhotos.get(taxonName)) {
67
+ data.push([
68
+ taxonName,
69
+ photo.id,
70
+ photo.ext,
71
+ photo.licenseCode,
72
+ photo.attrName ?? "",
73
+ ]);
74
+ }
75
+ }
76
+
77
+ CSV.writeFile(`${options.outputdir}/${PHOTO_FILE_NAME}`, data, headers);
78
+ }
79
+
80
+ /**
81
+ * @param {import("commander").OptionValues} options
82
+ * @param {import("commander").OptionValues} commandOptions
83
+ */
84
+ async function checkmissing(options, commandOptions) {
85
+ const taxa = await getTaxa(options);
86
+ const errorLog = new ErrorLog(options.outputdir + "/log.tsv", true);
87
+
88
+ const minPhotos = commandOptions.minphotos;
89
+ for (const taxon of taxa.getTaxonList()) {
90
+ const photos = taxon.getPhotos();
91
+ if (
92
+ minPhotos === undefined
93
+ ? photos.length !== MAX_PHOTOS
94
+ : photos.length < minPhotos
95
+ ) {
96
+ errorLog.log(taxon.getName(), photos.length.toString());
97
+ }
98
+ }
99
+
100
+ errorLog.write();
101
+ }
102
+
103
+ /**
104
+ * @param {import("commander").OptionValues} options
105
+ * @return {Promise<Taxa>}
106
+ */
107
+ async function getTaxa(options) {
108
+ const errorLog = new ErrorLog(options.outputdir + "/errors.tsv", true);
109
+
110
+ const loader = options[OPT_LOADER];
111
+ let taxa;
112
+ if (loader) {
113
+ const taxaLoaderClass = await import("file:" + path.resolve(loader));
114
+ taxa = await taxaLoaderClass.TaxaLoader.loadTaxa(options, errorLog);
115
+ } else {
116
+ taxa = new Taxa(
117
+ Program.getIncludeList(options.datadir),
118
+ errorLog,
119
+ options.showFlowerErrors,
120
+ );
121
+ }
122
+
123
+ errorLog.write();
124
+ return taxa;
125
+ }
126
+
127
+ /**
128
+ * @returns {Map<string,InatPhotoInfo[]>}
129
+ */
130
+ function readPhotos() {
131
+ const photosFileName = `./data/${PHOTO_FILE_NAME}`;
132
+ if (!existsSync(photosFileName)) {
133
+ return new Map();
134
+ }
135
+
136
+ /** @type {Map<string,{id:string,ext:string,licenseCode:string,attrName:string}[]>} */
137
+ const taxonPhotos = new Map();
138
+
139
+ /** @type {InatCsvPhoto[]} */
140
+ const csvPhotos = CSV.readFile(photosFileName);
141
+ for (const csvPhoto of csvPhotos) {
142
+ const taxonName = csvPhoto.name;
143
+ let photos = taxonPhotos.get(taxonName);
144
+ if (!photos) {
145
+ photos = [];
146
+ taxonPhotos.set(taxonName, photos);
147
+ }
148
+ photos.push({
149
+ id: csvPhoto.id.toString(),
150
+ ext: csvPhoto.ext,
151
+ licenseCode: csvPhoto.licenseCode,
152
+ attrName: csvPhoto.attrName,
153
+ });
154
+ }
155
+
156
+ return taxonPhotos;
157
+ }
158
+
159
+ const program = Program.getProgram();
160
+ program
161
+ .command("checkmissing")
162
+ .description("List taxa with less than the maximum number of photos")
163
+ .option(
164
+ "--minphotos <number>",
165
+ "Minimum number of photos. Taxa with fewer than this number will be listed.",
166
+ )
167
+ .action((options) => checkmissing(program.opts(), options));
168
+ if (process.env.npm_package_name === "@ca-plant-list/ca-plant-list") {
169
+ // Only allow updates in ca-plant-list.
170
+ program
171
+ .command("addmissing")
172
+ .description("Add photos to taxa with fewer than the maximum")
173
+ .action(() => addMissingPhotos(program.opts()));
174
+ }
175
+ program.option(
176
+ "--loader <path>",
177
+ "The path (relative to the current directory) of the JavaScript file containing the TaxaLoader class. If not provided, the default TaxaLoader will be used.",
178
+ );
179
+ await program.parseAsync();
@@ -0,0 +1,21 @@
1
+ {
2
+ "calflora": {
3
+ "counties": [
4
+ "ALA",
5
+ "CCA"
6
+ ]
7
+ },
8
+ "inat": {
9
+ "project": "ebcnps"
10
+ },
11
+ "labels": {
12
+ "introduced": "Introduced to the East Bay",
13
+ "native": "Native to the East Bay",
14
+ "status-NC": "California native introduced to the East Bay"
15
+ },
16
+ "ebook": {
17
+ "filename": "ebplants",
18
+ "pub_id": "ebplants",
19
+ "title": "East Bay Plants"
20
+ }
21
+ }
@@ -0,0 +1,93 @@
1
+ {
2
+ "Campanula exigua": {
3
+ "rpi": {
4
+ "translation-to-rpi": "Ravenella exigua"
5
+ }
6
+ },
7
+ "Campanula sharsmithiae": {
8
+ "rpi": {
9
+ "translation-to-rpi": "Ravenella sharsmithiae"
10
+ }
11
+ },
12
+ "Castilleja ambigua subsp. ambigua": {
13
+ "rpi": {
14
+ "translation-to-rpi": "Castilleja ambigua var. ambigua"
15
+ }
16
+ },
17
+ "Castilleja ambigua var. ambigua": {
18
+ "rpi": {
19
+ "translation": "Castilleja ambigua subsp. ambigua"
20
+ }
21
+ },
22
+ "Downingia ornatissima var. mirabilis": {
23
+ "inat": {
24
+ "notintaxondata": true
25
+ }
26
+ },
27
+ "Erysimum capitatum var. angustatum": {
28
+ "calflora": {
29
+ "badjepsonid": true
30
+ },
31
+ "jepson": {
32
+ "allowsynonym": true
33
+ }
34
+ },
35
+ "Heterotheca oregona var. rudis": {
36
+ "inat": {
37
+ "notintaxondata": true
38
+ }
39
+ },
40
+ "Horkelia californica var. frondosa": {
41
+ "inat": {
42
+ "notintaxondata": true
43
+ }
44
+ },
45
+ "Lupinus littoralis var. variicolor": {
46
+ "inat": {
47
+ "notintaxondata": true
48
+ }
49
+ },
50
+ "Malacothamnus hallii": {
51
+ "comment": "in CNPS Rare Plant Inventory",
52
+ "calflora": {
53
+ "notintaxondata": true
54
+ },
55
+ "jepson": {
56
+ "allowsynonym": true
57
+ },
58
+ "rpi": {
59
+ "translation": "Malacothamnus arcuatus var. elmeri"
60
+ }
61
+ },
62
+ "Malacothamnus arcuatus var. elmeri": {
63
+ "comment": "in CNPS Rare Plant Inventory as Malacothamnus hallii",
64
+ "rpi": {
65
+ "translation-to-rpi": "Malacothamnus hallii"
66
+ }
67
+ },
68
+ "Myosurus minimus subsp. apus": {
69
+ "comment": "in CNPS Rare Plant Inventory",
70
+ "jepson": {
71
+ "notineflora": true
72
+ }
73
+ },
74
+ "Ravenella exigua": {
75
+ "comment": "in CNPS Rare Plant Inventory as Ravenella",
76
+ "rpi": {
77
+ "translation": "Campanula exigua"
78
+ }
79
+ },
80
+ "Ravenella sharsmithiae": {
81
+ "rpi": {
82
+ "translation": "Campanula sharsmithiae"
83
+ }
84
+ },
85
+ "Streptanthus albidus subsp. peramoenus": {
86
+ "calflora": {
87
+ "notintaxondata": true
88
+ },
89
+ "jepson": {
90
+ "allowsynonym": true
91
+ }
92
+ }
93
+ }