@ca-plant-list/ca-plant-list 0.4.19 → 0.4.20

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.
Files changed (41) hide show
  1. package/data/genera.json +36 -32
  2. package/data/synonyms.csv +2 -0
  3. package/data/taxa.csv +6 -5
  4. package/data/text/Rumex-conglomeratus.md +1 -0
  5. package/data/text/Rumex-obtusifolius.md +1 -0
  6. package/data/text/Rumex-pulcher.md +1 -0
  7. package/data/text/Rumex-salicifolius.md +1 -0
  8. package/lib/ebook/glossarypages.js +3 -3
  9. package/lib/ebook/images.js +2 -2
  10. package/lib/ebook/pages/page_list_families.js +1 -1
  11. package/lib/ebook/pages/page_list_flower_color.js +2 -2
  12. package/lib/ebook/pages/page_list_flowers.js +8 -8
  13. package/lib/ebook/pages/tocpage.js +2 -2
  14. package/lib/ebook/plantbook.js +2 -2
  15. package/lib/externalsites.js +20 -15
  16. package/lib/families.js +1 -1
  17. package/lib/flowercolor.js +2 -2
  18. package/lib/genera.js +2 -2
  19. package/lib/index.d.ts +31 -1
  20. package/lib/photo.js +1 -10
  21. package/lib/plants/glossary.js +2 -4
  22. package/lib/program.js +10 -2
  23. package/lib/sitegenerator.js +13 -3
  24. package/lib/taxa.js +14 -10
  25. package/lib/taxon.js +6 -3
  26. package/lib/tools/calflora.js +41 -8
  27. package/lib/tools/calscape.js +4 -4
  28. package/lib/tools/inat.js +7 -7
  29. package/lib/tools/jepsoneflora.js +28 -4
  30. package/lib/tools/jepsonfamilies.js +102 -0
  31. package/lib/tools/rpi.js +5 -5
  32. package/lib/tools/supplementaltext.js +43 -0
  33. package/lib/tools/taxacsv.js +2 -2
  34. package/lib/utils/inat-tools.js +39 -2
  35. package/lib/web/glossarypages.js +6 -6
  36. package/package.json +6 -7
  37. package/scripts/cpl-photos.js +2 -2
  38. package/scripts/cpl-tools.js +11 -3
  39. package/scripts/inatobsphotos.js +10 -1
  40. package/scripts/inattaxonphotos.js +45 -43
  41. package/types/classes.d.ts +0 -205
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.4.19",
3
+ "version": "0.4.20",
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": {
@@ -14,8 +14,7 @@
14
14
  "ebook",
15
15
  "jekyll",
16
16
  "lib",
17
- "scripts",
18
- "types"
17
+ "scripts"
19
18
  ],
20
19
  "exports": {
21
20
  ".": "./lib/index.js"
@@ -36,7 +35,7 @@
36
35
  "inatobsphotos": "scripts/inatobsphotos.js"
37
36
  },
38
37
  "dependencies": {
39
- "@htmltools/scrape": "^0.1.0",
38
+ "@htmltools/scrape": "^0.1.1",
40
39
  "archiver": "^5.3.1",
41
40
  "cli-progress": "^3.12.0",
42
41
  "commander": "^12.1.0",
@@ -54,11 +53,11 @@
54
53
  "@types/cli-progress": "^3.11.6",
55
54
  "@types/jest": "^29.5.14",
56
55
  "@types/markdown-it": "^14.1.2",
57
- "@types/node": "^22.10.3",
56
+ "@types/node": "^22.10.7",
58
57
  "@types/unzipper": "^0.10.9",
59
- "eslint": "^9.17.0",
58
+ "eslint": "^9.18.0",
60
59
  "jest": "^29.7.0",
61
60
  "prettier": "^3.4.2",
62
- "typescript": "^5.7.2"
61
+ "typescript": "^5.7.3"
63
62
  }
64
63
  }
@@ -129,7 +129,7 @@ async function getTaxa(options) {
129
129
  }
130
130
 
131
131
  /**
132
- * @returns {Map<string,InatPhotoInfo[]>}
132
+ * @returns {Map<string,import("../lib/utils/inat-tools.js").InatPhotoInfo[]>}
133
133
  */
134
134
  function readPhotos() {
135
135
  const photosFileName = `./data/${PHOTO_FILE_NAME}`;
@@ -140,7 +140,7 @@ function readPhotos() {
140
140
  /** @type {Map<string,{id:string,ext:string,licenseCode:string,attrName:string}[]>} */
141
141
  const taxonPhotos = new Map();
142
142
 
143
- /** @type {InatCsvPhoto[]} */
143
+ /** @type {import("../lib/utils/inat-tools.js").InatCsvPhoto[]} */
144
144
  // @ts-ignore
145
145
  const csvPhotos = CSV.readFile(photosFileName);
146
146
  for (const csvPhoto of csvPhotos) {
@@ -11,6 +11,8 @@ import { JepsonEFlora } from "../lib/tools/jepsoneflora.js";
11
11
  import { RPI } from "../lib/tools/rpi.js";
12
12
  import { Config } from "../lib/config.js";
13
13
  import { Taxa } from "../lib/taxa.js";
14
+ import { SupplementalText } from "../lib/tools/supplementaltext.js";
15
+ import { JepsonFamilies } from "../lib/tools/jepsonfamilies.js";
14
16
 
15
17
  const TOOLS = {
16
18
  CALFLORA: "calflora",
@@ -58,9 +60,11 @@ async function build(program, options) {
58
60
  case TOOLS.CALFLORA:
59
61
  await Calflora.analyze(
60
62
  TOOLS_DATA_DIR,
63
+ options.datadir,
61
64
  taxa,
62
65
  exceptions,
63
66
  errorLog,
67
+ !!options.update,
64
68
  );
65
69
  break;
66
70
  case TOOLS.CALSCAPE:
@@ -86,11 +90,15 @@ async function build(program, options) {
86
90
  break;
87
91
  case TOOLS.JEPSON_EFLORA: {
88
92
  const eflora = new JepsonEFlora(TOOLS_DATA_DIR, taxa, errorLog);
89
- await eflora.analyze(exceptions, !!options.update);
93
+ await eflora.analyze(
94
+ options.datadir,
95
+ exceptions,
96
+ !!options.update,
97
+ );
90
98
  break;
91
99
  }
92
100
  case TOOLS.JEPSON_FAM:
93
- // await JepsonFamilies.build(TOOLS_DATA_DIR, options.outputdir);
101
+ await JepsonFamilies.build(TOOLS_DATA_DIR, "./data");
94
102
  break;
95
103
  case TOOLS.RPI:
96
104
  await RPI.analyze(
@@ -102,7 +110,7 @@ async function build(program, options) {
102
110
  );
103
111
  break;
104
112
  case TOOLS.TEXT:
105
- // SupplementalText.analyze(taxa, errorLog);
113
+ SupplementalText.analyze(taxa, errorLog);
106
114
  break;
107
115
  default:
108
116
  console.log("unrecognized tool: " + tool);
@@ -9,6 +9,15 @@ import { Program } from "../lib/program.js";
9
9
  import { Taxa } from "../lib/taxa.js";
10
10
  import { sleep } from "../lib/util.js";
11
11
 
12
+ /**
13
+ * @typedef {{
14
+ observation_photos: {
15
+ photo: import("../lib/utils/inat-tools.js").InatApiPhoto;
16
+ }[];
17
+ }} InatApiObservation
18
+ @typedef {import("../lib/program.js").CommandLineOptions &{filename?:string,inatObsQuery?:string}} InatObsPhotosCommandLineOptions
19
+ */
20
+
12
21
  // While I'm guessing the products of this data will be non-commercial, it's
13
22
  // not clear how they'll be licensed so the ShareAlike clause is out, and
14
23
  // they'll probably be derivative works so the "No Derivatives" clause should
@@ -18,7 +27,7 @@ const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
18
27
  const DEFAULT_FILENAME = "inatobsphotos.csv";
19
28
 
20
29
  /**
21
- * @param {Taxon} taxon
30
+ * @param {import("../lib/taxon.js").Taxon} taxon
22
31
  * @param {InatObsPhotosCommandLineOptions} options
23
32
  * @return {Promise<InatApiObservation[]>}
24
33
  */
@@ -10,24 +10,26 @@ import { Program } from "../lib/program.js";
10
10
  import { Taxa } from "../lib/taxa.js";
11
11
  import { chunk, sleep } from "../lib/util.js";
12
12
 
13
+ /**
14
+ * @typedef {import("../lib/program.js").CommandLineOptions & {filename?:string}} InatTaxonPhotosCommandLineOptions
15
+ */
16
+
13
17
  // While I'm guessing the products of this data will be non-commercial, it's
14
18
  // not clear how they'll be licensed so the ShareAlike clause is out, and
15
19
  // they'll probably be derivative works so the "No Derivatives" clause should
16
20
  // be respected.
17
- const ALLOWED_LICENSE_CODES = [
18
- "cc0", "cc-by", "cc-by-nc"
19
- ];
21
+ const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
20
22
 
21
23
  const DEFAULT_FILENAME = "inattaxonphotos.csv";
22
24
 
23
25
  /**
24
- * @param {Taxon[]} taxa
25
- * @return {Promise<InatApiTaxon[]>}
26
+ * @param {import("../lib/taxon.js").Taxon[]} taxa
27
+ * @return {Promise<import("../lib/utils/inat-tools.js").InatApiTaxon[]>}
26
28
  */
27
- async function fetchInatTaxa( taxa ) {
28
- const inatTaxonIDs = taxa.map( taxon => taxon.getINatID( ) ).filter( Boolean );
29
- const url = `https://api.inaturalist.org/v2/taxa/${inatTaxonIDs.join( "," )}?fields=(taxon_photos:(photo:(medium_url:!t,attribution:!t,license_code:!t)))`;
30
- const resp = await fetch( url );
29
+ async function fetchInatTaxa(taxa) {
30
+ const inatTaxonIDs = taxa.map((taxon) => taxon.getINatID()).filter(Boolean);
31
+ const url = `https://api.inaturalist.org/v2/taxa/${inatTaxonIDs.join(",")}?fields=(taxon_photos:(photo:(medium_url:!t,attribution:!t,license_code:!t)))`;
32
+ const resp = await fetch(url);
31
33
  if (!resp.ok) {
32
34
  const error = await resp.text();
33
35
  throw new Error(`Failed to fetch taxa from iNat: ${error}`);
@@ -39,74 +41,74 @@ async function fetchInatTaxa( taxa ) {
39
41
  /**
40
42
  * @param {InatTaxonPhotosCommandLineOptions} options
41
43
  */
42
- async function getTaxonPhotos( options ) {
44
+ async function getTaxonPhotos(options) {
43
45
  const errorLog = new ErrorLog(options.outputdir + "/errors.tsv");
44
46
  const taxa = new Taxa(
45
47
  Program.getIncludeList(options.datadir),
46
48
  errorLog,
47
- false
49
+ false,
48
50
  );
49
- const targetTaxa = taxa.getTaxonList( );
51
+ const targetTaxa = taxa.getTaxonList();
50
52
 
51
53
  const filename = path.join("data", options.filename || DEFAULT_FILENAME);
52
- const writableStream = fs.createWriteStream( filename );
53
- const columns = [
54
- "name",
55
- "id",
56
- "ext",
57
- "licenseCode",
58
- "attrName",
59
- ];
60
- const stringifier = stringify( { header: true, columns: columns } );
54
+ const writableStream = fs.createWriteStream(filename);
55
+ const columns = ["name", "id", "ext", "licenseCode", "attrName"];
56
+ const stringifier = stringify({ header: true, columns: columns });
61
57
  stringifier.pipe(writableStream);
62
58
  const prog = new cliProgress.SingleBar({
63
59
  format: "Downloading [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}",
64
- etaBuffer: targetTaxa.length
60
+ etaBuffer: targetTaxa.length,
65
61
  });
66
- prog.setMaxListeners( 100 );
67
- prog.start( targetTaxa.length, 0 );
62
+ prog.setMaxListeners(100);
63
+ prog.start(targetTaxa.length, 0);
68
64
 
69
65
  // Fetch endpoint can load multiple taxa, but it will created some long URLs so best to keep this smallish
70
- for ( const batch of chunk( targetTaxa, 30 ) ) {
71
- const inatTaxa = await fetchInatTaxa( batch );
72
- for ( const taxon of batch ) {
73
- prog.increment( );
74
- const iNatTaxon = inatTaxa.find( it => it.id === Number( taxon.getINatID() ) );
75
- if ( !iNatTaxon ) continue;
76
- // Just get the CC-licensed ones, 5 per taxon should be fine (max is 20 on iNat). Whether or not
66
+ for (const batch of chunk(targetTaxa, 30)) {
67
+ const inatTaxa = await fetchInatTaxa(batch);
68
+ for (const taxon of batch) {
69
+ prog.increment();
70
+ const iNatTaxon = inatTaxa.find(
71
+ (it) => it.id === Number(taxon.getINatID()),
72
+ );
73
+ if (!iNatTaxon) continue;
74
+ // Just get the CC-licensed ones, 5 per taxon should be fine (max is 20 on iNat). Whether or not
77
75
  const taxonPhotos = iNatTaxon.taxon_photos
78
- .filter( tp => ALLOWED_LICENSE_CODES.includes( tp.photo.license_code ) )
79
- .slice( 0, 5 );
76
+ .filter((tp) =>
77
+ ALLOWED_LICENSE_CODES.includes(tp.photo.license_code),
78
+ )
79
+ .slice(0, 5);
80
80
 
81
- for ( const taxonPhoto of taxonPhotos ) {
81
+ for (const taxonPhoto of taxonPhotos) {
82
82
  const row = [
83
83
  taxon.getName(),
84
84
  taxonPhoto.photo.id,
85
- String( taxonPhoto.photo.medium_url ).split( "." ).at( -1 ),
85
+ String(taxonPhoto.photo.medium_url).split(".").at(-1),
86
86
  // Need the license code to do attribution properly
87
87
  taxonPhoto.photo.license_code,
88
88
  // Photographers retain copyright for most CC licenses,
89
89
  // except CC0, so attribution is a bit different
90
- (
91
- taxonPhoto.photo.attribution.match( /\(c\) (.*?),/ )?.[1]
92
- || taxonPhoto.photo.attribution.match( /uploaded by (.*)/ )?.[1]
93
- )
90
+ taxonPhoto.photo.attribution.match(/\(c\) (.*?),/)?.[1] ||
91
+ taxonPhoto.photo.attribution.match(
92
+ /uploaded by (.*)/,
93
+ )?.[1],
94
94
  ];
95
- stringifier.write( row );
95
+ stringifier.write(row);
96
96
  }
97
97
  }
98
98
  // iNat will throttle you if you make more than 1 request a second.
99
99
  // See https://www.inaturalist.org/pages/api+recommended+practices
100
- await sleep( 1_100 );
100
+ await sleep(1_100);
101
101
  }
102
102
  prog.stop();
103
103
  }
104
104
 
105
105
  const program = Program.getProgram();
106
- program.action(getTaxonPhotos).description( "Write a CSV to datadir with iNaturalist taxon photos" )
106
+ program
107
+ .action(getTaxonPhotos)
108
+ .description("Write a CSV to datadir with iNaturalist taxon photos")
107
109
  .option(
108
110
  "-fn, --filename <filename>",
109
- "Name of file to write to the data dir"
111
+ "Name of file to write to the data dir",
110
112
  );
111
113
 
112
114
  await program.parseAsync();
@@ -1,205 +0,0 @@
1
- declare class CommandLineOptions {
2
- datadir: string;
3
- outputdir: string;
4
- "show-flower-errors": boolean;
5
- }
6
-
7
- declare class Config {
8
- getConfigValue(
9
- prefix: string,
10
- name: string,
11
- subcategory?: string,
12
- defaultValue?: string,
13
- ): string | undefined;
14
- getCountyCodes(): string[];
15
- getLabel(name: string, dflt: string): string;
16
- }
17
-
18
- declare class ErrorLog {
19
- log(...args: any[]): void;
20
- write(): void;
21
- }
22
-
23
- declare class Families {
24
- getFamilies(): Family[];
25
- getFamily(name: string): Family;
26
- }
27
-
28
- declare class Family {
29
- getFileName(): string;
30
- getName(): string;
31
- getSectionName(): string;
32
- getTaxa(): Taxon[];
33
- }
34
-
35
- declare class FlowerColor {
36
- getColorCode(): string;
37
- getColorName(upperCase?: boolean): string;
38
- getFileName(): string;
39
- getTaxa(): Taxon[];
40
- }
41
-
42
- declare class Genera {
43
- getGenus(name: string): Genus;
44
- }
45
-
46
- declare class Genus {
47
- getFamily(): Family;
48
- getTaxa(): Taxon[];
49
- }
50
-
51
- declare class GlossaryEntry {
52
- getMarkdown(): string;
53
- getTermName(): string;
54
- }
55
-
56
- declare class InatObsOptions {
57
- coords?: [number, number];
58
- project_id?: string;
59
- subview?: "grid" | "list" | "map";
60
- taxon_id?: string;
61
- }
62
-
63
- declare class SiteGenerator {
64
- copyIllustrations(flowerColors: FlowerColor[]): void;
65
- mkdir(path: string): void;
66
- writeTemplate(
67
- content: string,
68
- attributes: Record<string, string>,
69
- filename: string,
70
- ): void;
71
- }
72
-
73
- declare class SynonymData {
74
- Current: string;
75
- Former: string;
76
- Type: string;
77
- }
78
-
79
- declare class Taxa {
80
- getFamilies(): Families;
81
- getFlowerColors(): FlowerColor[];
82
- getTaxon(name: string): Taxon;
83
- getTaxonList(): Taxon[];
84
- hasSynonym(name: string): boolean;
85
- isSubset(): boolean;
86
- }
87
-
88
- type StatusCode = "N" | "NC" | "U" | "X";
89
- declare class Taxon {
90
- getBaseFileName(): string;
91
- getBloomEnd(): number | undefined;
92
- getBloomStart(): number | undefined;
93
- getCalfloraID(): string;
94
- getCalfloraName(): string;
95
- getCalfloraTaxonLink(): string | undefined;
96
- getCalscapeCommonName(): string | undefined;
97
- getCalscapeName(): string;
98
- getCommonNames(): string[];
99
- getFamily(): Family;
100
- getFESA(): string | undefined;
101
- getFileName(): string;
102
- getFlowerColors(): string[] | undefined;
103
- getGenus(): Genus;
104
- getGenusName(): string;
105
- getGlobalRank(): string | undefined;
106
- getHTMLLink(
107
- href: boolean | string | undefined,
108
- includeRPI?: boolean,
109
- ): string;
110
- getINatID(): string;
111
- getINatName(): string;
112
- getINatSyn(): string | undefined;
113
- getINatTaxonLink(): string;
114
- getJepsonID(): string;
115
- getLifeCycle(): string;
116
- getName(): string;
117
- getRPIID(): string | undefined;
118
- getRPIRank(): string;
119
- getRPIRankAndThreat(): string;
120
- getRPIRankAndThreatTooltip(): string;
121
- getRPITaxonLink(): string;
122
- getStatus(): StatusCode;
123
- getStatusDescription(config: Config): string;
124
- getSynonyms(): string[];
125
- isCANative(): boolean;
126
- isNative(): boolean;
127
- }
128
-
129
- declare class TaxonData {
130
- bloom_end: string;
131
- bloom_start: string;
132
- calrecnum: string;
133
- calscape_cn?: string;
134
- CESA: string;
135
- "common name": string;
136
- CRPR: string;
137
- FESA: string;
138
- flower_color: string;
139
- GRank: string;
140
- "inat id": string;
141
- "jepson id": string;
142
- life_cycle: string;
143
- "RPI ID": string;
144
- SRank: string;
145
- status: StatusCode;
146
- taxon_name: string;
147
- }
148
-
149
- type PhotoRights = "CC0" | "CC BY" | "CC BY-NC" | "C" | null;
150
-
151
- type InatPhotoInfo = {
152
- id: string;
153
- ext: string;
154
- licenseCode: string;
155
- attrName: string | undefined;
156
- };
157
-
158
- type InatLicenseCode =
159
- | "cc-by-nc-sa"
160
- | "cc-by-nc"
161
- | "cc-by-nc-nd"
162
- | "cc-by"
163
- | "cc-by-sa"
164
- | "cc-by-nd"
165
- | "pd"
166
- | "gdfl"
167
- | "cc0";
168
-
169
- declare class InatCsvPhoto {
170
- name: string;
171
- id: number;
172
- ext: string;
173
- licenseCode: InatLicenseCode;
174
- attrName: string;
175
- }
176
-
177
- declare class InatApiPhoto {
178
- id: number;
179
- attribution: string;
180
- license_code: InatLicenseCode;
181
- medium_url?: string;
182
- url?: string;
183
- }
184
-
185
- declare class InatApiTaxon {
186
- id: number;
187
- taxon_photos: {
188
- photo: InatApiPhoto;
189
- }[];
190
- }
191
-
192
- declare class InatApiObservation {
193
- observation_photos: {
194
- photo: InatApiPhoto;
195
- }[];
196
- }
197
-
198
- declare class InatObsPhotosCommandLineOptions extends CommandLineOptions {
199
- filename?: string;
200
- inatObsQuery?: string;
201
- }
202
-
203
- declare class InatTaxonPhotosCommandLineOptions extends CommandLineOptions {
204
- filename?: string;
205
- }