@ca-plant-list/ca-plant-list 0.4.18 → 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 (51) hide show
  1. package/data/genera.json +36 -32
  2. package/data/inattaxonphotos.csv +4 -0
  3. package/data/synonyms.csv +2 -0
  4. package/data/taxa.csv +6 -5
  5. package/data/text/Polypodium-calirhiza.md +1 -0
  6. package/data/text/Polypodium-scouleri.md +1 -0
  7. package/data/text/Rumex-conglomeratus.md +1 -0
  8. package/data/text/Rumex-obtusifolius.md +1 -0
  9. package/data/text/Rumex-pulcher.md +1 -0
  10. package/data/text/Rumex-salicifolius.md +1 -0
  11. package/lib/basepagerenderer.js +3 -3
  12. package/lib/ebook/glossarypages.js +3 -3
  13. package/lib/ebook/images.js +7 -7
  14. package/lib/ebook/pages/page_list_families.js +1 -1
  15. package/lib/ebook/pages/page_list_flower_color.js +2 -2
  16. package/lib/ebook/pages/page_list_flowers.js +8 -8
  17. package/lib/ebook/pages/page_list_species.js +1 -1
  18. package/lib/ebook/pages/taxonpage.js +2 -2
  19. package/lib/ebook/pages/tocpage.js +2 -2
  20. package/lib/ebook/plantbook.js +3 -3
  21. package/lib/externalsites.js +20 -15
  22. package/lib/families.js +14 -14
  23. package/lib/flowercolor.js +2 -2
  24. package/lib/genera.js +3 -3
  25. package/lib/htmltaxon.js +16 -7
  26. package/lib/index.d.ts +46 -1
  27. package/lib/pagerenderer.js +223 -220
  28. package/lib/photo.js +52 -31
  29. package/lib/plants/glossary.js +2 -4
  30. package/lib/program.js +10 -2
  31. package/lib/sitegenerator.js +13 -3
  32. package/lib/taxa.js +16 -12
  33. package/lib/taxon.js +12 -6
  34. package/lib/tools/calflora.js +41 -8
  35. package/lib/tools/calscape.js +4 -4
  36. package/lib/tools/inat.js +7 -7
  37. package/lib/tools/jepsoneflora.js +28 -4
  38. package/lib/tools/jepsonfamilies.js +102 -0
  39. package/lib/tools/rpi.js +8 -9
  40. package/lib/tools/supplementaltext.js +43 -0
  41. package/lib/tools/taxacsv.js +2 -2
  42. package/lib/utils/inat-tools.js +39 -2
  43. package/lib/web/glossarypages.js +6 -6
  44. package/lib/web/pagetaxon.js +4 -6
  45. package/package.json +10 -8
  46. package/scripts/cpl-photos.js +2 -2
  47. package/scripts/cpl-tools.js +11 -3
  48. package/scripts/inatobsphotos.js +10 -1
  49. package/scripts/inattaxonphotos.js +45 -43
  50. package/lib/inat_photo.js +0 -43
  51. package/types/classes.d.ts +0 -232
package/lib/tools/rpi.js CHANGED
@@ -12,10 +12,10 @@ class RPI {
12
12
 
13
13
  /**
14
14
  * @param {string} toolsDataDir
15
- * @param {Taxa} taxa
16
- * @param {Config} config
15
+ * @param {import("../taxa.js").Taxa} taxa
16
+ * @param {import("../config.js").Config} config
17
17
  * @param {import("../exceptions.js").Exceptions} exceptions
18
- * @param {ErrorLog} errorLog
18
+ * @param {import("../errorlog.js").ErrorLog} errorLog
19
19
  */
20
20
  static async analyze(toolsDataDir, taxa, config, exceptions, errorLog) {
21
21
  /**
@@ -202,11 +202,10 @@ class RPI {
202
202
  }
203
203
 
204
204
  /**
205
- *
206
- * @param {Taxa} taxa
207
- * @param {Config} config
205
+ * @param {import("../taxa.js").Taxa} taxa
206
+ * @param {import("../config.js").Config} config
208
207
  * @param {import("../exceptions.js").Exceptions} exceptions
209
- * @param {ErrorLog} errorLog
208
+ * @param {import("../errorlog.js").ErrorLog} errorLog
210
209
  */
211
210
  static #checkExceptions(taxa, config, exceptions, errorLog) {
212
211
  const countyCodes = config.getCountyCodes();
@@ -367,9 +366,9 @@ class RPI {
367
366
 
368
367
  /**
369
368
  * @param {string} toolsDataDir
370
- * @param {Taxa} taxa
369
+ * @param {import("../taxa.js").Taxa} taxa
371
370
  * @param {import("../exceptions.js").Exceptions} exceptions
372
- * @param {ErrorLog} errorLog
371
+ * @param {import("../errorlog.js").ErrorLog} errorLog
373
372
  */
374
373
  static async #scrape(toolsDataDir, taxa, exceptions, errorLog) {
375
374
  const toolsDataPath = toolsDataDir + "/rpi";
@@ -0,0 +1,43 @@
1
+ import { Files } from "../files.js";
2
+
3
+ const VALID_EXTENSIONS = new Set(["md", "footer.md"]);
4
+
5
+ export class SupplementalText {
6
+ /**
7
+ *
8
+ * @param {import("../taxa.js").Taxa} taxa
9
+ * @param {import("../errorlog.js").ErrorLog} errorLog
10
+ */
11
+ static analyze(taxa, errorLog) {
12
+ /**
13
+ * @param {string} fileName
14
+ */
15
+ function fileNameToTaxonName(fileName) {
16
+ const parts = fileName.split(".");
17
+ const ext = parts.slice(1).join(".");
18
+ const taxonName = parts[0]
19
+ .replace("-", " ")
20
+ .replace("-var-", " var. ")
21
+ .replace("-subsp-", " subsp. ");
22
+ return { taxonName: taxonName, ext: ext };
23
+ }
24
+
25
+ const dirName = "data/text";
26
+
27
+ if (!Files.isDir(dirName)) {
28
+ return;
29
+ }
30
+
31
+ const entries = Files.getDirEntries(dirName);
32
+ for (const entry of entries) {
33
+ const parsed = fileNameToTaxonName(entry);
34
+ const taxon = taxa.getTaxon(parsed.taxonName);
35
+ if (!taxon) {
36
+ errorLog.log(dirName + "/" + entry, "not found in taxa.csv");
37
+ }
38
+ if (!VALID_EXTENSIONS.has(parsed.ext)) {
39
+ errorLog.log(dirName + "/" + entry, "has invalid extension");
40
+ }
41
+ }
42
+ }
43
+ }
@@ -4,7 +4,7 @@ import { CSV } from "../csv.js";
4
4
  export class TaxaCSV {
5
5
  #filePath;
6
6
  #headers;
7
- /** @type {TaxonData[]} */
7
+ /** @type {import("../index.js").TaxonData[]} */
8
8
  #taxa;
9
9
 
10
10
  /**
@@ -19,7 +19,7 @@ export class TaxaCSV {
19
19
  }
20
20
 
21
21
  /**
22
- * @returns {TaxonData[]}
22
+ * @returns {import("../index.js").TaxonData[]}
23
23
  */
24
24
  getTaxa() {
25
25
  return this.#taxa;
@@ -1,10 +1,47 @@
1
1
  import { ProgressMeter } from "../progressmeter.js";
2
2
  import { chunk, sleep } from "../util.js";
3
3
 
4
+ /**
5
+ @typedef {"cc-by-nc-sa"
6
+ | "cc-by-nc"
7
+ | "cc-by-nc-nd"
8
+ | "cc-by"
9
+ | "cc-by-sa"
10
+ | "cc-by-nd"
11
+ | "pd"
12
+ | "gdfl"
13
+ | "cc0"} InatLicenseCode
14
+ @typedef {{
15
+ id: string;
16
+ ext: string;
17
+ licenseCode: string;
18
+ attrName: string | undefined;
19
+ }} InatPhotoInfo
20
+ @typedef {{
21
+ id: number;
22
+ attribution: string;
23
+ license_code: InatLicenseCode;
24
+ medium_url?: string;
25
+ url?: string;
26
+ }} InatApiPhoto
27
+ @typedef {{
28
+ id: number;
29
+ taxon_photos: {
30
+ photo: InatApiPhoto;
31
+ }[];
32
+ }} InatApiTaxon
33
+ @typedef {{
34
+ name: string;
35
+ id: number;
36
+ ext: string;
37
+ licenseCode: InatLicenseCode;
38
+ attrName: string;
39
+ }} InatCsvPhoto
40
+ */
4
41
  const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
5
42
 
6
43
  /**
7
- * @param {Taxon[]} taxa
44
+ * @param {import("../taxon.js").Taxon[]} taxa
8
45
  * @return {Promise<InatApiTaxon[]>}
9
46
  */
10
47
  async function fetchInatTaxa(taxa) {
@@ -20,7 +57,7 @@ async function fetchInatTaxa(taxa) {
20
57
  }
21
58
 
22
59
  /**
23
- * @param {Taxon[]} taxaToUpdate
60
+ * @param {import("../taxon.js").Taxon[]} taxaToUpdate
24
61
  * @returns {Promise<Map<string,InatPhotoInfo[]>>}
25
62
  */
26
63
  export async function getTaxonPhotos(taxaToUpdate) {
@@ -10,7 +10,7 @@ class GlossaryPages {
10
10
  #glossary;
11
11
 
12
12
  /**
13
- * @param {SiteGenerator} siteGenerator
13
+ * @param {import("../sitegenerator.js").SiteGenerator} siteGenerator
14
14
  */
15
15
  constructor(siteGenerator) {
16
16
  this.#siteGenerator = siteGenerator;
@@ -18,7 +18,7 @@ class GlossaryPages {
18
18
  }
19
19
 
20
20
  /**
21
- * @param {GlossaryEntry} entry
21
+ * @param {import("../plants/glossary.js").GlossaryEntry} entry
22
22
  */
23
23
  #generateEntryPage(entry) {
24
24
  const title = entry.getTermName();
@@ -29,7 +29,7 @@ class GlossaryPages {
29
29
  this.#siteGenerator.writeTemplate(
30
30
  html,
31
31
  { title: title },
32
- Files.join(ENTRY_DIR, title + ".html")
32
+ Files.join(ENTRY_DIR, title + ".html"),
33
33
  );
34
34
  }
35
35
 
@@ -50,8 +50,8 @@ class GlossaryPages {
50
50
  links.push(
51
51
  HTML.getLink(
52
52
  Files.join(ENTRY_DIR, entry.getHTMLFileName()),
53
- entry.getTermName()
54
- )
53
+ entry.getTermName(),
54
+ ),
55
55
  );
56
56
  }
57
57
  let html = HTML.wrap("h1", "Glossary");
@@ -59,7 +59,7 @@ class GlossaryPages {
59
59
  this.#siteGenerator.writeTemplate(
60
60
  html,
61
61
  { title: "Glossary" },
62
- "glossary.html"
62
+ "glossary.html",
63
63
  );
64
64
  }
65
65
 
@@ -11,8 +11,8 @@ class PageTaxon extends GenericPage {
11
11
 
12
12
  /**
13
13
  * @param {string} outputDir
14
- * @param {Config} config
15
- * @param {Taxon} taxon
14
+ * @param {import("../config.js").Config} config
15
+ * @param {import("../taxon.js").Taxon} taxon
16
16
  */
17
17
  constructor(outputDir, config, taxon) {
18
18
  super(outputDir, taxon.getName(), taxon.getBaseFileName());
@@ -189,7 +189,7 @@ class PageTaxon extends GenericPage {
189
189
 
190
190
  html += HTMLTaxon.getFooterHTML(this.#taxon);
191
191
 
192
- const photos = this.#taxon.getPhotos().slice( 0, 5 );
192
+ const photos = this.#taxon.getPhotos().slice(0, 5);
193
193
  if (photos.length > 0) {
194
194
  let photosHtml = "";
195
195
  for (const photo of photos) {
@@ -202,9 +202,7 @@ class PageTaxon extends GenericPage {
202
202
  />
203
203
  </a>
204
204
  <figcaption>
205
- ${photo.rights === "CC0" ? "By" : "(c)"}
206
- ${photo.rightsHolder}
207
- ${photo.rights && `(${photo.rights})`}
205
+ ${photo.getAttribution()}
208
206
  </figcaption>
209
207
  </figure>
210
208
  `;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ca-plant-list/ca-plant-list",
3
- "version": "0.4.18",
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,16 +14,16 @@
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"
22
21
  },
23
22
  "types": "./lib/index.d.ts",
24
23
  "scripts": {
25
- "check": "npm run eslint && npm run tsc",
24
+ "check": "npm run eslint && npm run tsc && npm run jest",
26
25
  "eslint": "npx eslint",
26
+ "jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js tests",
27
27
  "prettier": "npx prettier -l .",
28
28
  "tsc": "npx tsc"
29
29
  },
@@ -35,7 +35,7 @@
35
35
  "inatobsphotos": "scripts/inatobsphotos.js"
36
36
  },
37
37
  "dependencies": {
38
- "@htmltools/scrape": "^0.1.0",
38
+ "@htmltools/scrape": "^0.1.1",
39
39
  "archiver": "^5.3.1",
40
40
  "cli-progress": "^3.12.0",
41
41
  "commander": "^12.1.0",
@@ -51,11 +51,13 @@
51
51
  "devDependencies": {
52
52
  "@types/archiver": "^6.0.2",
53
53
  "@types/cli-progress": "^3.11.6",
54
+ "@types/jest": "^29.5.14",
54
55
  "@types/markdown-it": "^14.1.2",
55
- "@types/node": "^22.10.3",
56
+ "@types/node": "^22.10.7",
56
57
  "@types/unzipper": "^0.10.9",
57
- "eslint": "^9.17.0",
58
+ "eslint": "^9.18.0",
59
+ "jest": "^29.7.0",
58
60
  "prettier": "^3.4.2",
59
- "typescript": "^5.7.2"
61
+ "typescript": "^5.7.3"
60
62
  }
61
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();
package/lib/inat_photo.js DELETED
@@ -1,43 +0,0 @@
1
- import { CC0, CC_BY, CC_BY_NC, COPYRIGHT, Photo } from "./photo.js";
2
-
3
- class InatPhoto extends Photo {
4
- /** @type {number} */
5
- inatPhotoId;
6
- /** @type {string} */
7
- ext;
8
-
9
- /**
10
- * @param {number} id
11
- * @param {string} ext
12
- * @param {InatLicenseCode} licenseCode
13
- * @param {string} attrName
14
- */
15
- constructor(id, ext, licenseCode, attrName) {
16
- /** @type {typeof COPYRIGHT | typeof CC_BY | typeof CC_BY_NC | typeof CC0} */
17
- let rights = COPYRIGHT;
18
- if (licenseCode === "cc0") rights = CC0;
19
- else if (licenseCode === "cc-by") rights = CC_BY;
20
- else if (licenseCode === "cc-by-nc") rights = CC_BY_NC;
21
- super(null, attrName, rights);
22
- this.inatPhotoId = id;
23
- this.ext = ext;
24
- }
25
-
26
- getExt() {
27
- return this.ext;
28
- }
29
-
30
- getId() {
31
- return this.inatPhotoId;
32
- }
33
-
34
- getUrl() {
35
- return `https://inaturalist-open-data.s3.amazonaws.com/photos/${this.inatPhotoId}/medium.${this.ext}`;
36
- }
37
-
38
- getSourceUrl() {
39
- return `https://www.inaturalist.org/photos/${this.inatPhotoId}`;
40
- }
41
- }
42
-
43
- export { InatPhoto };