@ca-plant-list/ca-plant-list 0.4.15 → 0.4.16
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/README.md +24 -0
- package/data/inatobsphotos.csv +9286 -0
- package/data/inattaxonphotos.csv +27 -0
- package/data/taxa.csv +4 -4
- package/lib/csv.js +1 -26
- package/lib/exceptions.js +2 -1
- package/lib/program.js +8 -4
- package/lib/taxa.js +76 -19
- package/lib/taxon.js +8 -2
- package/lib/tools/calflora.js +5 -0
- package/lib/tools/jepsoneflora.js +440 -0
- package/lib/tools/rpi.js +435 -0
- package/lib/utils/inat-tools.js +4 -4
- package/lib/web/pagetaxon.js +1 -1
- package/package.json +22 -6
- package/scripts/cpl-photos.js +1 -0
- package/scripts/cpl-tools.js +21 -47
- package/scripts/dev/publish.sh +3 -0
- package/scripts/inatobsphotos.js +113 -0
- package/scripts/inattaxonphotos.js +10 -4
- package/types/classes.d.ts +33 -8
- package/.github/workflows/main.yml +0 -20
- package/.prettierrc +0 -3
- package/.vscode/settings.json +0 -9
- package/eslint.config.mjs +0 -13
- package/schemas/exceptions.schema.json +0 -62
- package/tmp/config.json +0 -21
- package/tmp/exceptions.json +0 -93
- package/tmp/families.json +0 -790
- package/tmp/genera.json +0 -5566
- package/tmp/inattaxonphotos.csv +0 -6059
- package/tmp/synonyms.csv +0 -2141
- package/tmp/taxa_include.csv +0 -19
@@ -0,0 +1,113 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import fs from "fs";
|
4
|
+
import cliProgress from "cli-progress";
|
5
|
+
import { stringify } from "csv-stringify";
|
6
|
+
import path from "path";
|
7
|
+
|
8
|
+
import { Program } from "../lib/program.js";
|
9
|
+
import { Taxa } from "../lib/taxa.js";
|
10
|
+
import { sleep } from "../lib/util.js";
|
11
|
+
|
12
|
+
// While I'm guessing the products of this data will be non-commercial, it's
|
13
|
+
// not clear how they'll be licensed so the ShareAlike clause is out, and
|
14
|
+
// they'll probably be derivative works so the "No Derivatives" clause should
|
15
|
+
// be respected.
|
16
|
+
const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
|
17
|
+
|
18
|
+
const DEFAULT_FILENAME = "inatobsphotos.csv";
|
19
|
+
|
20
|
+
/**
|
21
|
+
* @param {Taxon} taxon
|
22
|
+
* @param {InatObsPhotosCommandLineOptions} options
|
23
|
+
* @return {Promise<InatApiObservation[]>}
|
24
|
+
*/
|
25
|
+
async function fetchObservationsForTaxon(taxon, options) {
|
26
|
+
const inatTaxonId = taxon.getINatID();
|
27
|
+
if (!inatTaxonId) return [];
|
28
|
+
let url =
|
29
|
+
`https://api.inaturalist.org/v2/observations/?taxon_id=${inatTaxonId}` +
|
30
|
+
"&photo_license=" +
|
31
|
+
ALLOWED_LICENSE_CODES.join(",") +
|
32
|
+
"&order=desc" +
|
33
|
+
"&order_by=votes" +
|
34
|
+
"&per_page=5" +
|
35
|
+
"&fields=(observation_photos:(photo:(url:!t,attribution:!t,license_code:!t)))";
|
36
|
+
if (typeof options.inatObsQuery === "string") {
|
37
|
+
url += `&${options.inatObsQuery}`;
|
38
|
+
}
|
39
|
+
const resp = await fetch(url);
|
40
|
+
if (!resp.ok) {
|
41
|
+
const error = await resp.text();
|
42
|
+
throw new Error(`Failed to fetch taxa from iNat: ${error}`);
|
43
|
+
}
|
44
|
+
const json = await resp.json();
|
45
|
+
return json.results;
|
46
|
+
}
|
47
|
+
|
48
|
+
/**
|
49
|
+
* @param {InatObsPhotosCommandLineOptions} options
|
50
|
+
*/
|
51
|
+
async function getObsPhotos(options) {
|
52
|
+
console.log("[inatobsphotos.js] options", options);
|
53
|
+
|
54
|
+
const taxa = await Taxa.loadTaxa(options);
|
55
|
+
const targetTaxa = taxa.getTaxonList();
|
56
|
+
|
57
|
+
const filename = path.join("data", options.filename || DEFAULT_FILENAME);
|
58
|
+
const writableStream = fs.createWriteStream(filename);
|
59
|
+
const columns = ["name", "id", "ext", "licenseCode", "attrName"];
|
60
|
+
const stringifier = stringify({ header: true, columns: columns });
|
61
|
+
stringifier.pipe(writableStream);
|
62
|
+
const prog = new cliProgress.SingleBar({
|
63
|
+
format: "Downloading [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}",
|
64
|
+
etaBuffer: targetTaxa.length,
|
65
|
+
});
|
66
|
+
prog.setMaxListeners(100);
|
67
|
+
prog.start(targetTaxa.length, 0);
|
68
|
+
|
69
|
+
for (const taxon of targetTaxa) {
|
70
|
+
prog.increment();
|
71
|
+
const observations = await fetchObservationsForTaxon(taxon, options);
|
72
|
+
// Just get the CC-licensed ones, 5 per taxon should be fine (max is 20 on iNat). Whether or not
|
73
|
+
const photos = observations
|
74
|
+
.map((obs) => obs.observation_photos.map((op) => op.photo))
|
75
|
+
.flat()
|
76
|
+
.filter((photo) =>
|
77
|
+
ALLOWED_LICENSE_CODES.includes(photo.license_code),
|
78
|
+
)
|
79
|
+
.slice(0, 5);
|
80
|
+
for (const photo of photos) {
|
81
|
+
const row = [
|
82
|
+
taxon.getName(),
|
83
|
+
photo.id,
|
84
|
+
String(photo.url).split(".").at(-1),
|
85
|
+
// Need the license code to do attribution properly
|
86
|
+
photo.license_code,
|
87
|
+
// Photographers retain copyright for most CC licenses,
|
88
|
+
// except CC0, so attribution is a bit different
|
89
|
+
photo.attribution.match(/\(c\) (.*?),/)?.[1] ||
|
90
|
+
photo.attribution.match(/uploaded by (.*)/)?.[1],
|
91
|
+
];
|
92
|
+
stringifier.write(row);
|
93
|
+
}
|
94
|
+
await sleep(1_100);
|
95
|
+
}
|
96
|
+
prog.stop();
|
97
|
+
}
|
98
|
+
|
99
|
+
const program = Program.getProgram();
|
100
|
+
program
|
101
|
+
.action(getObsPhotos)
|
102
|
+
.description("Write a CSV to datadir with iNaturalist observation photos")
|
103
|
+
.option(
|
104
|
+
"-q, --inat-obs-query <query>",
|
105
|
+
"Additional iNat observations API query terms to add, e.g. place_id=1234&d1=2020-01-01",
|
106
|
+
)
|
107
|
+
.option(
|
108
|
+
"-fn, --filename <filename>",
|
109
|
+
"Name of file to write to the data dir",
|
110
|
+
DEFAULT_FILENAME,
|
111
|
+
);
|
112
|
+
|
113
|
+
await program.parseAsync();
|
@@ -18,6 +18,8 @@ const ALLOWED_LICENSE_CODES = [
|
|
18
18
|
"cc0", "cc-by", "cc-by-nc"
|
19
19
|
];
|
20
20
|
|
21
|
+
const DEFAULT_FILENAME = "inattaxonphotos.csv";
|
22
|
+
|
21
23
|
/**
|
22
24
|
* @param {Taxon[]} taxa
|
23
25
|
* @return {Promise<InatApiTaxon[]>}
|
@@ -35,7 +37,7 @@ async function fetchInatTaxa( taxa ) {
|
|
35
37
|
}
|
36
38
|
|
37
39
|
/**
|
38
|
-
* @param {
|
40
|
+
* @param {InatTaxonPhotosCommandLineOptions} options
|
39
41
|
*/
|
40
42
|
async function getTaxonPhotos( options ) {
|
41
43
|
const errorLog = new ErrorLog(options.outputdir + "/errors.tsv");
|
@@ -46,7 +48,7 @@ async function getTaxonPhotos( options ) {
|
|
46
48
|
);
|
47
49
|
const targetTaxa = taxa.getTaxonList( );
|
48
50
|
|
49
|
-
const filename = path.join(
|
51
|
+
const filename = path.join("data", options.filename || DEFAULT_FILENAME);
|
50
52
|
const writableStream = fs.createWriteStream( filename );
|
51
53
|
const columns = [
|
52
54
|
"name",
|
@@ -80,7 +82,7 @@ async function getTaxonPhotos( options ) {
|
|
80
82
|
const row = [
|
81
83
|
taxon.getName(),
|
82
84
|
taxonPhoto.photo.id,
|
83
|
-
taxonPhoto.photo.medium_url.split( "." ).at( -1 ),
|
85
|
+
String( taxonPhoto.photo.medium_url ).split( "." ).at( -1 ),
|
84
86
|
// Need the license code to do attribution properly
|
85
87
|
taxonPhoto.photo.license_code,
|
86
88
|
// Photographers retain copyright for most CC licenses,
|
@@ -101,6 +103,10 @@ async function getTaxonPhotos( options ) {
|
|
101
103
|
}
|
102
104
|
|
103
105
|
const program = Program.getProgram();
|
104
|
-
program.action(getTaxonPhotos).description( "Write a CSV to datadir with iNaturalist taxon photos" )
|
106
|
+
program.action(getTaxonPhotos).description( "Write a CSV to datadir with iNaturalist taxon photos" )
|
107
|
+
.option(
|
108
|
+
"-fn, --filename <filename>",
|
109
|
+
"Name of file to write to the data dir"
|
110
|
+
);
|
105
111
|
|
106
112
|
await program.parseAsync();
|
package/types/classes.d.ts
CHANGED
@@ -16,7 +16,7 @@ declare class Config {
|
|
16
16
|
}
|
17
17
|
|
18
18
|
declare class ErrorLog {
|
19
|
-
log(...args:
|
19
|
+
log(...args: any[]): void;
|
20
20
|
write(): void;
|
21
21
|
}
|
22
22
|
|
@@ -83,6 +83,7 @@ declare class Taxa {
|
|
83
83
|
getFlowerColors(): FlowerColor[];
|
84
84
|
getTaxon(name: string): Taxon;
|
85
85
|
getTaxonList(): Taxon[];
|
86
|
+
hasSynonym(name: string): boolean;
|
86
87
|
isSubset(): boolean;
|
87
88
|
}
|
88
89
|
|
@@ -92,6 +93,7 @@ declare class TaxaCol {
|
|
92
93
|
title: string;
|
93
94
|
}
|
94
95
|
|
96
|
+
type StatusCode = "N" | "NC" | "U" | "X";
|
95
97
|
declare class Taxon {
|
96
98
|
constructor(data: TaxonData, genera: Genera, meta: any);
|
97
99
|
getBaseFileName(): string;
|
@@ -103,6 +105,7 @@ declare class Taxon {
|
|
103
105
|
getCalscapeCommonName(): string | undefined;
|
104
106
|
getCalscapeName(): string;
|
105
107
|
getCESA(): string | undefined;
|
108
|
+
getCNDDBRank(): string | undefined;
|
106
109
|
getCommonNames(): string[];
|
107
110
|
getFamily(): Family;
|
108
111
|
getFESA(): string | undefined;
|
@@ -110,21 +113,25 @@ declare class Taxon {
|
|
110
113
|
getFlowerColors(): string[] | undefined;
|
111
114
|
getGenus(): Genus;
|
112
115
|
getGenusName(): string;
|
116
|
+
getGlobalRank(): string | undefined;
|
113
117
|
getHTMLLink(
|
114
118
|
href: boolean | string | undefined,
|
115
119
|
includeRPI?: boolean,
|
116
120
|
): string;
|
117
121
|
getINatID(): string;
|
118
122
|
getINatName(): string;
|
123
|
+
getINatSyn(): string | undefined;
|
119
124
|
getINatTaxonLink(): string;
|
120
125
|
getJepsonID(): string;
|
121
126
|
getLifeCycle(): string;
|
122
127
|
getName(): string;
|
123
128
|
getPhotos(): Photo[];
|
129
|
+
getRPIID(): string | undefined;
|
124
130
|
getRPIRank(): string;
|
125
131
|
getRPIRankAndThreat(): string;
|
126
132
|
getRPIRankAndThreatTooltip(): string;
|
127
133
|
getRPITaxonLink(): string;
|
134
|
+
getStatus(): StatusCode;
|
128
135
|
getStatusDescription(config: Config): string;
|
129
136
|
getSynonyms(): string[];
|
130
137
|
isCANative(): boolean;
|
@@ -147,7 +154,7 @@ declare class TaxonData {
|
|
147
154
|
life_cycle: string;
|
148
155
|
"RPI ID": string;
|
149
156
|
SRank: string;
|
150
|
-
status:
|
157
|
+
status: StatusCode;
|
151
158
|
taxon_name: string;
|
152
159
|
}
|
153
160
|
|
@@ -194,14 +201,32 @@ declare class InatCsvPhoto {
|
|
194
201
|
attrName: string;
|
195
202
|
}
|
196
203
|
|
204
|
+
declare class InatApiPhoto {
|
205
|
+
id: number;
|
206
|
+
attribution: string;
|
207
|
+
license_code: InatLicenseCode;
|
208
|
+
medium_url?: string;
|
209
|
+
url?: string;
|
210
|
+
}
|
211
|
+
|
197
212
|
declare class InatApiTaxon {
|
198
213
|
id: number;
|
199
214
|
taxon_photos: {
|
200
|
-
photo:
|
201
|
-
id: number;
|
202
|
-
attribution: string;
|
203
|
-
license_code: InatLicenseCode;
|
204
|
-
medium_url: string;
|
205
|
-
};
|
215
|
+
photo: InatApiPhoto;
|
206
216
|
}[];
|
207
217
|
}
|
218
|
+
|
219
|
+
declare class InatApiObservation {
|
220
|
+
observation_photos: {
|
221
|
+
photo: InatApiPhoto;
|
222
|
+
}[]
|
223
|
+
}
|
224
|
+
|
225
|
+
declare class InatObsPhotosCommandLineOptions extends CommandLineOptions {
|
226
|
+
filename?: string;
|
227
|
+
inatObsQuery?: string;
|
228
|
+
}
|
229
|
+
|
230
|
+
declare class InatTaxonPhotosCommandLineOptions extends CommandLineOptions {
|
231
|
+
filename?: string;
|
232
|
+
}
|
@@ -1,20 +0,0 @@
|
|
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
DELETED
package/.vscode/settings.json
DELETED
package/eslint.config.mjs
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
import globals from "globals";
|
2
|
-
import pluginJs from "@eslint/js";
|
3
|
-
|
4
|
-
/** @type {import('eslint').Linter.Config[]} */
|
5
|
-
export default [
|
6
|
-
{ files: ["**/*.{js,mjs,cjs}"], ignores: ["output/**"] },
|
7
|
-
{
|
8
|
-
languageOptions: {
|
9
|
-
globals: { ...globals.browser, ...globals.node, bootstrap: false },
|
10
|
-
},
|
11
|
-
},
|
12
|
-
pluginJs.configs.recommended,
|
13
|
-
];
|
@@ -1,62 +0,0 @@
|
|
1
|
-
{
|
2
|
-
"type": "object",
|
3
|
-
"additionalProperties": {
|
4
|
-
"type": "object",
|
5
|
-
"properties": {
|
6
|
-
"calflora": {
|
7
|
-
"type": "object",
|
8
|
-
"properties": {
|
9
|
-
"badjepsonid": {
|
10
|
-
"const": true
|
11
|
-
},
|
12
|
-
"native": { "type": "boolean" },
|
13
|
-
"notintaxondata": {
|
14
|
-
"const": true
|
15
|
-
}
|
16
|
-
},
|
17
|
-
"additionalProperties": false
|
18
|
-
},
|
19
|
-
"calscape": {
|
20
|
-
"type": "object",
|
21
|
-
"properties": { "notnative": { "const": true } }
|
22
|
-
},
|
23
|
-
"comment": {
|
24
|
-
"type": "string"
|
25
|
-
},
|
26
|
-
"inat": {
|
27
|
-
"type": "object",
|
28
|
-
"properties": {
|
29
|
-
"notintaxondata": {
|
30
|
-
"const": true
|
31
|
-
}
|
32
|
-
},
|
33
|
-
"additionalProperties": false
|
34
|
-
},
|
35
|
-
"jepson": {
|
36
|
-
"type": "object",
|
37
|
-
"properties": {
|
38
|
-
"allowsynonym": {
|
39
|
-
"const": true
|
40
|
-
},
|
41
|
-
"notineflora": {
|
42
|
-
"const": true
|
43
|
-
}
|
44
|
-
},
|
45
|
-
"additionalProperties": false
|
46
|
-
},
|
47
|
-
"rpi": {
|
48
|
-
"type": "object",
|
49
|
-
"properties": {
|
50
|
-
"translation": {
|
51
|
-
"type": "string"
|
52
|
-
},
|
53
|
-
"translation-to-rpi": {
|
54
|
-
"type": "string"
|
55
|
-
}
|
56
|
-
},
|
57
|
-
"additionalProperties": false
|
58
|
-
}
|
59
|
-
},
|
60
|
-
"additionalProperties": false
|
61
|
-
}
|
62
|
-
}
|
package/tmp/config.json
DELETED
@@ -1,21 +0,0 @@
|
|
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
|
-
}
|
package/tmp/exceptions.json
DELETED
@@ -1,93 +0,0 @@
|
|
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
|
-
}
|