@ca-plant-list/ca-plant-list 0.4.31 → 0.4.32
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/data/inatobsphotos.csv +9318 -9281
- package/data/inattaxonphotos.csv +467 -69
- package/data/synonyms.csv +2 -0
- package/data/taxa.csv +14 -13
- package/data/text/Brodiaea-elegans-subsp-elegans.md +1 -0
- package/data/text/Brodiaea-terrestris-subsp-terrestris.md +1 -0
- package/lib/photo.js +6 -4
- package/lib/taxonomy/taxa.js +3 -22
- package/lib/utils/inat-tools.js +211 -43
- package/package.json +1 -1
- package/scripts/cpl-photos.js +334 -62
package/data/synonyms.csv
CHANGED
@@ -686,6 +686,7 @@ Erigeron peregrinus var. callianthemus,Erigeron glacialis var. glacialis
|
|
686
686
|
Eriocoma lemmonii subsp. lemmonii,Stipa lemmonii var. lemmonii,INAT
|
687
687
|
Eriogonella membranacea,Chorizanthe membranacea
|
688
688
|
Eriogonum acetoselloides,Eriogonum gracile var. gracile
|
689
|
+
Eriogonum arachnoideum,Eriogonum latifolium
|
689
690
|
Eriogonum auriculatum,Eriogonum nudum var. auriculatum
|
690
691
|
Eriogonum caninum,Eriogonum luteolum var. caninum
|
691
692
|
Eriogonum curvatum,Eriogonum wrightii var. subscaposum
|
@@ -706,6 +707,7 @@ Eriogonum lobbii var. minus,Eriogonum lobbii
|
|
706
707
|
Eriogonum marifolium var. incanum,Eriogonum incanum
|
707
708
|
Eriogonum nivale,Eriogonum ovalifolium var. nivale
|
708
709
|
Eriogonum nudum subsp. pauciflorum,Eriogonum nudum var. pauciflorum
|
710
|
+
Eriogonum oblongifolium,Eriogonum latifolium
|
709
711
|
Eriogonum polifolium,Eriogonum fasciculatum var. polifolium
|
710
712
|
Eriogonum polyanthum var. bahiiforme,Eriogonum umbellatum var. bahiiforme
|
711
713
|
Eriogonum revolutum,Eriogonum fasciculatum var. polifolium
|
package/data/taxa.csv
CHANGED
@@ -237,7 +237,7 @@ Brickellia californica,California brickelbush,N,1794,1152,58803,202493,true,Bric
|
|
237
237
|
Briza maxima,quaking grass,X,16135,1165,57157,136989,true
|
238
238
|
Briza minor,little quaking grass,X,16137,1166,57162,136993,true
|
239
239
|
Brodiaea elegans subsp. elegans,harvest brodiaea,N,49461,1175,57127,202520,true,Harvest Brodiaea,corm,purple,4,8
|
240
|
-
Brodiaea terrestris subsp. terrestris,,N,49464,1188,59081,202533,true,Dwarf Brodiaea,corm
|
240
|
+
Brodiaea terrestris subsp. terrestris,,N,49464,1188,59081,202533,true,Dwarf Brodiaea,corm,purple,4,7
|
241
241
|
Bromus arenarius,Australian chess,X,16216,1191,58368,137101,true
|
242
242
|
Bromus berteroanus,Chilean chess,X,16219,9740,75897,137110,true
|
243
243
|
Bromus catharticus var. catharticus,rescue grass,X,85279,11433,80494,169088,true
|
@@ -275,9 +275,9 @@ Callitriche marginata,water-starwort,N,16692,1259,58093,64128,true,Winged Water
|
|
275
275
|
Callitriche trochlearis,,N,16702,1261,69994,64137,true,Effluent Water-starwort
|
276
276
|
Calochortus albus,white globe lily,N,16710,1264,51315,202570,true,White Globe Lily,,white,4,6
|
277
277
|
Calochortus argillosus,clay mariposa lily,N,76542,1268,64330,202576,true,Clay Mariposa Lily,bulb,"white,pink",4,6
|
278
|
-
Calochortus clavatus var. pallidus
|
279
|
-
Calochortus invenustus
|
280
|
-
Calochortus leichtlinii
|
278
|
+
Calochortus clavatus var. pallidus,,N,55455,1275,58360,202583,true,Pale Yellow Mariposa
|
279
|
+
Calochortus invenustus,,N,16733,1284,64432,202590,true,Plain Mariposa Lily
|
280
|
+
Calochortus leichtlinii,,N,16735,1288,52542,202594,true,Leichtlin's Mariposa Lily
|
281
281
|
Calochortus luteus,yellow mariposa lily,N,16737,1290,51077,202595,true,Yellow Mariposa Lily,bulb,yellow,4,6
|
282
282
|
Calochortus pulchellus,Mt. Diablo fairy lantern,N,16755,1303,47326,202609,true,Mt. Diablo Fairy Lantern,bulb,,,,50,1B.2,,,S2,G2
|
283
283
|
Calochortus splendens,,N,16759,1306,58361,202611,true,Splendid Mariposa Lily,bulb
|
@@ -364,7 +364,7 @@ Ceanothus thyrsiflorus,blue blossom,N,18501,1830,56617,12790,true,Blueblossom Ce
|
|
364
364
|
Centaurea calcitrapa,purple star-thistle,X,1922,1845,56865,202776,true
|
365
365
|
Centaurea cyanus,"bachelor's button,cornflower",X,1925,1846,933404,202778,true
|
366
366
|
Centaurea iberica,Iberian starthistle,X,1931,1849,76212,206620,true
|
367
|
-
Centaurea melitensis,"tocalote,Maltese
|
367
|
+
Centaurea melitensis,"tocalote,Maltese star-thistle",X,1934,1851,57922,202785,true
|
368
368
|
Centaurea solstitialis,yellow star-thistle,X,1946,1853,52588,202793,true
|
369
369
|
Centranthus ruber,red valerian,X,18595,1864,57283,202799
|
370
370
|
Centromadia fitchii,Fitch's spikeweed,N,77593,10809,58804,202801,true,Fitch Spikeweed
|
@@ -443,7 +443,7 @@ Claytonia exigua subsp. glauca,,N,76949,2233,57229,127855,true,Serpentine Spring
|
|
443
443
|
Claytonia gypsophiloides,candy stripers,N,19620,2234,55344,112597,true,Gypsum Springbeauty,annual,pink,3,5
|
444
444
|
Claytonia parviflora subsp. parviflora,,N,74695,2242,58296,127861,true,Utah Miner's Lettuce,"annual,perennial",white,3,6
|
445
445
|
Claytonia perfoliata subsp. mexicana,,N,76956,2245,53004,127866,true,Southern Miner's Lettuce,annual,white,2,4
|
446
|
-
Claytonia perfoliata subsp. perfoliata,
|
446
|
+
Claytonia perfoliata subsp. perfoliata,miner's lettuce,N,49921,2246,53002,127867,true,Miner's Lettuce,annual,white,1,5
|
447
447
|
Claytonia rubra subsp. depressa,red stem spring beauty,N,76957,2248,79751,127869,true,Miner's Lettuce,annual,white,2,4
|
448
448
|
Clematis lasiantha,pipestems,N,19682,2254,49876,13749,true,Chaparral Clematis,,white,1,6
|
449
449
|
Clematis ligusticifolia,virgin's bower,N,19683,2255,58996,13750,true,Virgin's Bower,,white,6,9
|
@@ -701,6 +701,7 @@ Eriogonum fasciculatum var. polifolium,,N,58546,3247,58272,129983,true,Interior
|
|
701
701
|
Eriogonum gracile var. gracile,slender buckwheat,N,58563,3257,58982,129999,true,Slender Woolly Wild Buckwheat
|
702
702
|
Eriogonum incanum,frosted buckwheat,N,24809,3279,62680,114044,true,Frosted Wild Buckwheat,perennial,yellow,6,9
|
703
703
|
Eriogonum inerme var. inerme,unarmed buckwheat,N,58586,9110,80799,130026,true,Unarmed Wild Buckwheat
|
704
|
+
Eriogonum latifolium,seaside wild buckwheat,N,24832,3293,57211,114060,true,Coast Buckwheat,perennial,"white,pink",1,12
|
704
705
|
Eriogonum lobbii,granite buckwheat,N,24839,9935,77005,114067,true,Lobb's Buckwheat,perennial,"white,pink",6,8
|
705
706
|
Eriogonum luteolum var. caninum,Tiburon buckwheat,N,58623,3297,61205,130067,true,Tiburon Buckwheat,,,,,733,1B.2,,,S2,G5T2
|
706
707
|
Eriogonum luteolum var. luteolum,,N,58624,3298,64226,130068,true,Golden-carpet Wild Buckwheat
|
@@ -1089,10 +1090,10 @@ Logfia gallica,,X,31363,9521,56949,204736,true,,annual,"brown,yellow",3,7
|
|
1089
1090
|
Lomatium californicum,chu-chu-pate,N,31389,4950,56824,204737,,California Lomatium,,yellow,4,6
|
1090
1091
|
Lomatium caruifolium var. caruifolium,,N,61026,4953,64328,204739,,Alkali Desertparsley
|
1091
1092
|
Lomatium caruifolium var. denticulatum,,N,61027,4954,60123,204740
|
1092
|
-
Lomatium dasycarpum subsp. dasycarpum,,N,51334,4960,56829,204743,,Woolly Fruited Lomatium
|
1093
|
-
Lomatium macrocarpum,,N,31433,4974,56826,204754,,Bigseed Biscuitroot
|
1093
|
+
Lomatium dasycarpum subsp. dasycarpum,,N,51334,4960,56829,204743,,Woolly Fruited Lomatium,perennial,white,3,6
|
1094
|
+
Lomatium macrocarpum,,N,31433,4974,56826,204754,,Bigseed Biscuitroot,perennial,white,4,6
|
1094
1095
|
Lomatium nudicaule,,N,31441,4983,77818,204759,,Barestem Biscuitroot
|
1095
|
-
Lomatium utriculatum,,N,31473,4999,56827,204766,,Common Lomatium
|
1096
|
+
Lomatium utriculatum,,N,31473,4999,56827,204766,,Common Lomatium,perennial,yellow,2,5
|
1096
1097
|
Lonicera hispidula,California honeysuckle,N,31505,10075,53416,204768,,Pink Honeysuckle,,pink,5,6
|
1097
1098
|
Lonicera interrupta,chaparral honeysuckle,N,31506,5009,60878,204770,,Chaparral Honeysuckle,,"yellow,white",4,5
|
1098
1099
|
Lonicera involucrata var. ledebourii,black twinberry,N,61081,5012,60990,204773,,Twinberry Honeysuckle
|
@@ -1196,8 +1197,8 @@ Mercurialis annua,annual mercury,X,33300,5450,68838,25939,true
|
|
1196
1197
|
Mesembryanthemum nodiflorum,,X,33363,5461,71191,116177,true
|
1197
1198
|
Micranthes californica,,N,91942,11841,78023,26022,true,California Saxifrage
|
1198
1199
|
Micropus amphibolus,,N,33474,5462,78034,204946,true,Mount Diablo Cottonseed,,,,,1507,3.2,,,S3S4,G3G4
|
1199
|
-
Micropus californicus var. californicus,slender cottonweed,N,7993,5464,58030,204950,true,Cottontop
|
1200
|
-
Micropus californicus var. subvestitus
|
1200
|
+
Micropus californicus var. californicus,slender cottonweed,N,7993,5464,58030,204950,true,Cottontop,annual,white,3,6
|
1201
|
+
Micropus californicus var. subvestitus,Q-tips,N,7994,5465,81107,207081,true,Q-tips,annual,white,4,6
|
1201
1202
|
Microseris acuminata,,N,4146,5466,60142,204953,true,Sierra Foothill Silverpuffs
|
1202
1203
|
Microseris bigelovii,,N,4147,5467,60977,204954,true,Coastal Silverpuffs
|
1203
1204
|
Microseris campestris,,N,4149,5469,58031,204955,true,San Joaquin Silverpuffs
|
@@ -1833,8 +1834,8 @@ Triphysaria eriantha subsp. eriantha,butter-and-eggs,N,53279,8129,59332,96567,tr
|
|
1833
1834
|
Triphysaria pusilla,,N,47296,8133,57276,78297,true,Dwarf Owl's-clover
|
1834
1835
|
Triphysaria versicolor subsp. faucibarbata,,N,53281,8135,60737,206269,true,Triphysaria
|
1835
1836
|
Trisetum canescens,,N,47326,8139,1538928,150155,true,Tall False Oat
|
1836
|
-
Triteleia hyacinthina,white brodiaea,N,47356,8154,57132,206275,true,White Brodiaea
|
1837
|
-
Triteleia laxa,Ithuriel's spear,N,47359,8160,51085,206281,true,Ithuriel's Spear
|
1837
|
+
Triteleia hyacinthina,white brodiaea,N,47356,8154,57132,206275,true,White Brodiaea,corm,white,3,7
|
1838
|
+
Triteleia laxa,Ithuriel's spear,N,47359,8160,51085,206281,true,Ithuriel's Spear,corm,blue,3,6
|
1838
1839
|
Triteleia peduncularis,,N,47363,8164,57133,206284,true,Long Rayed Brodiaea
|
1839
1840
|
Tropaeolum majus,garden nasturtium,X,47408,8167,54329,34321,true
|
1840
1841
|
Tropidocarpum capparideum,caper-fruited tropidocarpum,N,47411,8168,79456,34327,true,Caper-fruited Tropidocarpum,annual,yellow,3,4,1255,1B.1,,,S1,G1
|
@@ -0,0 +1 @@
|
|
1
|
+
Base of flower funnel-shaped.
|
@@ -0,0 +1 @@
|
|
1
|
+
Base of flower bell-shaped.
|
package/lib/photo.js
CHANGED
@@ -10,10 +10,10 @@ export class Photo {
|
|
10
10
|
#rights;
|
11
11
|
|
12
12
|
/**
|
13
|
-
* @param {
|
13
|
+
* @param {string} id
|
14
14
|
* @param {string} ext
|
15
|
-
* @param {import("./utils/inat-tools.js").
|
16
|
-
* @param {string} rightsHolder
|
15
|
+
* @param {import("./utils/inat-tools.js").AllowedLicenseCode} licenseCode
|
16
|
+
* @param {string|undefined} rightsHolder
|
17
17
|
*/
|
18
18
|
constructor(id, ext, licenseCode, rightsHolder) {
|
19
19
|
this.#id = id;
|
@@ -22,7 +22,9 @@ export class Photo {
|
|
22
22
|
if (licenseCode === "cc0") this.#rights = "CC0";
|
23
23
|
else if (licenseCode === "cc-by") this.#rights = "CC BY";
|
24
24
|
else if (licenseCode === "cc-by-nc") this.#rights = "CC BY-NC";
|
25
|
-
else
|
25
|
+
else {
|
26
|
+
throw new Error(`unexpected license code ${licenseCode}`);
|
27
|
+
}
|
26
28
|
}
|
27
29
|
|
28
30
|
/**
|
package/lib/taxonomy/taxa.js
CHANGED
@@ -115,27 +115,7 @@ class Taxa {
|
|
115
115
|
#loadPhotosFromFile(dataDir, filename) {
|
116
116
|
if (!fs.existsSync(path.join(dataDir, filename))) return;
|
117
117
|
/** @type {import("../utils/inat-tools.js").InatCsvPhoto[]} */
|
118
|
-
const csvPhotos = CSV.
|
119
|
-
/** @type {import("../utils/inat-tools.js").InatLicenseCode} */
|
120
|
-
let licenseCode = "cc-by";
|
121
|
-
if (row.licenseCode === "cc-by-nc-sa") licenseCode = "cc-by-nc-sa";
|
122
|
-
else if (row.licenseCode === "cc-by-nc") licenseCode = "cc-by-nc";
|
123
|
-
else if (row.licenseCode === "cc-by-nc-nd")
|
124
|
-
licenseCode = "cc-by-nc-nd";
|
125
|
-
else if (row.licenseCode === "cc-by") licenseCode = "cc-by";
|
126
|
-
else if (row.licenseCode === "cc-by-sa") licenseCode = "cc-by-sa";
|
127
|
-
else if (row.licenseCode === "cc-by-nd") licenseCode = "cc-by-nd";
|
128
|
-
else if (row.licenseCode === "pd") licenseCode = "pd";
|
129
|
-
else if (row.licenseCode === "gdfl") licenseCode = "gdfl";
|
130
|
-
else if (row.licenseCode === "cc0") licenseCode = "cc0";
|
131
|
-
return {
|
132
|
-
attrName: row.attrName,
|
133
|
-
ext: row.ext,
|
134
|
-
id: Number(row.id),
|
135
|
-
licenseCode,
|
136
|
-
name: row.name,
|
137
|
-
};
|
138
|
-
});
|
118
|
+
const csvPhotos = CSV.readFile(path.join(dataDir, filename));
|
139
119
|
for (const csvPhoto of csvPhotos) {
|
140
120
|
const taxon = this.getTaxon(csvPhoto.name);
|
141
121
|
if (!taxon) {
|
@@ -156,8 +136,9 @@ class Taxa {
|
|
156
136
|
* @param {string} dataDir
|
157
137
|
*/
|
158
138
|
#loadInatPhotos(dataDir) {
|
159
|
-
|
139
|
+
// Prefer taxon photos, as they are somewhat curated.
|
160
140
|
this.#loadPhotosFromFile(dataDir, "inattaxonphotos.csv");
|
141
|
+
this.#loadPhotosFromFile("./data", "inatobsphotos.csv");
|
161
142
|
this.#loadPhotosFromFile(dataDir, "inatobsphotos.csv");
|
162
143
|
}
|
163
144
|
|
package/lib/utils/inat-tools.js
CHANGED
@@ -2,25 +2,25 @@ import { ProgressMeter } from "../progressmeter.js";
|
|
2
2
|
import { chunk, sleep } from "../util.js";
|
3
3
|
|
4
4
|
/**
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
| "cc0"} InatLicenseCode
|
5
|
+
* @typedef {"cc0" | "cc-by" | "cc-by-nc"} AllowedLicenseCode
|
6
|
+
* @typedef {{place_id?:string,project_id?:string}} ObsPhotoLocationOptions
|
7
|
+
@typedef {{
|
8
|
+
id:number,
|
9
|
+
observation_photos: {
|
10
|
+
photo: import("./inat-tools.js").InatApiPhoto;
|
11
|
+
}[];
|
12
|
+
}} InatApiObservationPhotos
|
14
13
|
@typedef {{
|
15
14
|
id: string;
|
15
|
+
obsId?:string;
|
16
16
|
ext: string;
|
17
|
-
licenseCode:
|
17
|
+
licenseCode: AllowedLicenseCode;
|
18
18
|
attrName: string | undefined;
|
19
19
|
}} InatPhotoInfo
|
20
20
|
@typedef {{
|
21
21
|
id: number;
|
22
22
|
attribution: string;
|
23
|
-
license_code:
|
23
|
+
license_code: string;
|
24
24
|
medium_url?: string;
|
25
25
|
url?: string;
|
26
26
|
}} InatApiPhoto
|
@@ -30,15 +30,38 @@ import { chunk, sleep } from "../util.js";
|
|
30
30
|
photo: InatApiPhoto;
|
31
31
|
}[];
|
32
32
|
}} InatApiTaxon
|
33
|
-
@typedef {{
|
34
|
-
name: string;
|
35
|
-
id: number;
|
36
|
-
ext: string;
|
37
|
-
licenseCode: InatLicenseCode;
|
38
|
-
attrName: string;
|
39
|
-
}} InatCsvPhoto
|
33
|
+
@typedef {{name: string;} & InatPhotoInfo} InatCsvPhoto
|
40
34
|
*/
|
35
|
+
|
41
36
|
const ALLOWED_LICENSE_CODES = ["cc0", "cc-by", "cc-by-nc"];
|
37
|
+
const FIELDS_OBS_PHOTO =
|
38
|
+
"(id:!t,observation_photos:(photo:(url:!t,attribution:!t,license_code:!t)))";
|
39
|
+
|
40
|
+
/**
|
41
|
+
* @param {InatApiPhoto} apiPhoto
|
42
|
+
* @returns {InatPhotoInfo|undefined}
|
43
|
+
*/
|
44
|
+
export function convertToCSVPhoto(apiPhoto) {
|
45
|
+
const licenseCode = getAllowedLicenseCode(apiPhoto.license_code);
|
46
|
+
if (licenseCode === undefined) {
|
47
|
+
return;
|
48
|
+
}
|
49
|
+
const url = apiPhoto.medium_url || apiPhoto.url;
|
50
|
+
if (!url) {
|
51
|
+
return;
|
52
|
+
}
|
53
|
+
const ext = url.split(".").at(-1);
|
54
|
+
if (!ext) {
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
/** @type {InatPhotoInfo} */
|
58
|
+
return {
|
59
|
+
id: apiPhoto.id.toString(),
|
60
|
+
ext: ext,
|
61
|
+
licenseCode: licenseCode,
|
62
|
+
attrName: getAttribution(apiPhoto.attribution),
|
63
|
+
};
|
64
|
+
}
|
42
65
|
|
43
66
|
/**
|
44
67
|
* @param {string[]} inatTaxonIDs
|
@@ -55,6 +78,122 @@ async function fetchInatTaxa(inatTaxonIDs) {
|
|
55
78
|
return json.results;
|
56
79
|
}
|
57
80
|
|
81
|
+
/**
|
82
|
+
* @param {import("../types.js").Taxon} taxon
|
83
|
+
* @param {{place_id?:string,project_id?:string}} locationOptions
|
84
|
+
* @return {Promise<InatApiObservationPhotos[]|Error>}
|
85
|
+
*/
|
86
|
+
async function fetchObservationsForTaxon(
|
87
|
+
taxon,
|
88
|
+
locationOptions = { place_id: "14" },
|
89
|
+
) {
|
90
|
+
const inatTaxonId = taxon.getINatID();
|
91
|
+
if (!inatTaxonId) return [];
|
92
|
+
let url = new URL(
|
93
|
+
`https://api.inaturalist.org/v2/observations/?taxon_id=${inatTaxonId}` +
|
94
|
+
"&photo_license=" +
|
95
|
+
ALLOWED_LICENSE_CODES.join(",") +
|
96
|
+
"&order=desc" +
|
97
|
+
"&order_by=votes" +
|
98
|
+
"&per_page=5",
|
99
|
+
);
|
100
|
+
url.searchParams.set("fields", FIELDS_OBS_PHOTO);
|
101
|
+
if (locationOptions.place_id) {
|
102
|
+
url.searchParams.set("place_id", locationOptions.place_id);
|
103
|
+
}
|
104
|
+
if (locationOptions.project_id) {
|
105
|
+
url.searchParams.set("project_id", locationOptions.project_id);
|
106
|
+
}
|
107
|
+
const resp = await getResponse(url);
|
108
|
+
if (resp instanceof Error) {
|
109
|
+
return resp;
|
110
|
+
}
|
111
|
+
|
112
|
+
if (!resp.ok) {
|
113
|
+
return new Error(await resp.text());
|
114
|
+
}
|
115
|
+
const json = await resp.json();
|
116
|
+
return json.results;
|
117
|
+
}
|
118
|
+
|
119
|
+
/**
|
120
|
+
* @param {string[]} obsIds
|
121
|
+
* @returns {Promise<InatApiObservationPhotos[]|Error>}
|
122
|
+
*/
|
123
|
+
export async function getObsPhotosForIds(obsIds) {
|
124
|
+
let url = new URL("https://api.inaturalist.org/v2/observations/");
|
125
|
+
url.searchParams.set("fields", FIELDS_OBS_PHOTO);
|
126
|
+
url.searchParams.set("id", obsIds.join(","));
|
127
|
+
url.searchParams.set("per_page", obsIds.length.toString());
|
128
|
+
|
129
|
+
const resp = await getResponse(url);
|
130
|
+
if (resp instanceof Error) {
|
131
|
+
return resp;
|
132
|
+
}
|
133
|
+
|
134
|
+
if (!resp.ok) {
|
135
|
+
return new Error(await resp.text());
|
136
|
+
}
|
137
|
+
const json = await resp.json();
|
138
|
+
return json.results;
|
139
|
+
}
|
140
|
+
|
141
|
+
/**
|
142
|
+
* @param {import("../types.js").Taxon[]} taxaToUpdate
|
143
|
+
* @param {ObsPhotoLocationOptions|undefined} locationOptions
|
144
|
+
* @returns {Promise<Map<string,InatPhotoInfo[]>>}
|
145
|
+
*/
|
146
|
+
export async function getObsPhotosForTaxa(taxaToUpdate, locationOptions) {
|
147
|
+
/** @type {Map<string,InatPhotoInfo[]>} */
|
148
|
+
const photos = new Map();
|
149
|
+
|
150
|
+
const meter = new ProgressMeter(
|
151
|
+
"retrieving observation photos",
|
152
|
+
taxaToUpdate.length,
|
153
|
+
);
|
154
|
+
|
155
|
+
for (let index = 0; index < taxaToUpdate.length; index++) {
|
156
|
+
const taxon = taxaToUpdate[index];
|
157
|
+
const observations = await fetchObservationsForTaxon(
|
158
|
+
taxon,
|
159
|
+
locationOptions,
|
160
|
+
);
|
161
|
+
if (observations instanceof Error) {
|
162
|
+
console.error(observations.message);
|
163
|
+
continue;
|
164
|
+
}
|
165
|
+
|
166
|
+
// Just get the CC-licensed ones, 5 per taxon should be fine (max is 20 on iNat). Whether or not
|
167
|
+
const rawPhotoInfo = observations
|
168
|
+
.map((obs) =>
|
169
|
+
obs.observation_photos.map((op) => {
|
170
|
+
return { obsId: obs.id, ...op.photo };
|
171
|
+
}),
|
172
|
+
)
|
173
|
+
.flat();
|
174
|
+
|
175
|
+
/** @type {InatPhotoInfo[]} */
|
176
|
+
const processedPhotoInfo = [];
|
177
|
+
for (const photo of rawPhotoInfo) {
|
178
|
+
if (processedPhotoInfo.length >= 5) {
|
179
|
+
break;
|
180
|
+
}
|
181
|
+
const obj = convertToCSVPhoto(photo);
|
182
|
+
if (!obj) {
|
183
|
+
continue;
|
184
|
+
}
|
185
|
+
processedPhotoInfo.push(obj);
|
186
|
+
}
|
187
|
+
photos.set(taxon.getName(), processedPhotoInfo);
|
188
|
+
|
189
|
+
meter.update(index + 1);
|
190
|
+
}
|
191
|
+
|
192
|
+
meter.stop();
|
193
|
+
|
194
|
+
return photos;
|
195
|
+
}
|
196
|
+
|
58
197
|
/**
|
59
198
|
* @param {import("../types.js").Taxon[]} taxaToUpdate
|
60
199
|
* @returns {Promise<Map<string,InatPhotoInfo[]>>}
|
@@ -78,37 +217,17 @@ export async function getTaxonPhotos(taxaToUpdate) {
|
|
78
217
|
for (const batch of chunk(taxaToUpdate, 30)) {
|
79
218
|
const inatTaxa = await fetchInatTaxa(batch.map((t) => t.getINatID()));
|
80
219
|
for (const iNatTaxon of inatTaxa) {
|
81
|
-
const iNatTaxonPhotos = iNatTaxon.taxon_photos.filter((tp) =>
|
82
|
-
ALLOWED_LICENSE_CODES.includes(tp.photo.license_code),
|
83
|
-
);
|
84
|
-
|
85
220
|
const taxonName = idMap.get(iNatTaxon.id.toString());
|
86
221
|
if (!taxonName) {
|
87
222
|
throw new Error(`iNat id ${iNatTaxon.id} not found`);
|
88
223
|
}
|
89
224
|
/** @type {InatPhotoInfo[]} */
|
90
225
|
const taxonPhotos = [];
|
91
|
-
for (const taxonPhoto of
|
92
|
-
const
|
93
|
-
if (!
|
94
|
-
|
95
|
-
|
96
|
-
/** @type {InatPhotoInfo} */
|
97
|
-
const obj = {
|
98
|
-
id: taxonPhoto.photo.id.toString(),
|
99
|
-
ext: ext,
|
100
|
-
licenseCode: taxonPhoto.photo.license_code,
|
101
|
-
attrName:
|
102
|
-
// Photographers retain copyright for most CC licenses,
|
103
|
-
// except CC0, so attribution is a bit different
|
104
|
-
taxonPhoto.photo.attribution.match(
|
105
|
-
/\(c\) (.*?),/,
|
106
|
-
)?.[1] ||
|
107
|
-
taxonPhoto.photo.attribution.match(
|
108
|
-
/uploaded by (.*)/,
|
109
|
-
)?.[1],
|
110
|
-
};
|
111
|
-
|
226
|
+
for (const taxonPhoto of iNatTaxon.taxon_photos) {
|
227
|
+
const obj = convertToCSVPhoto(taxonPhoto.photo);
|
228
|
+
if (!obj) {
|
229
|
+
continue;
|
230
|
+
}
|
112
231
|
taxonPhotos.push(obj);
|
113
232
|
}
|
114
233
|
photos.set(taxonName, taxonPhotos);
|
@@ -122,3 +241,52 @@ export async function getTaxonPhotos(taxaToUpdate) {
|
|
122
241
|
|
123
242
|
return photos;
|
124
243
|
}
|
244
|
+
|
245
|
+
/**
|
246
|
+
* @param {string} licenseCode
|
247
|
+
* @returns {AllowedLicenseCode|undefined}
|
248
|
+
*/
|
249
|
+
function getAllowedLicenseCode(licenseCode) {
|
250
|
+
switch (licenseCode) {
|
251
|
+
case "cc0":
|
252
|
+
case "cc-by":
|
253
|
+
case "cc-by-nc":
|
254
|
+
return licenseCode;
|
255
|
+
}
|
256
|
+
}
|
257
|
+
|
258
|
+
/**
|
259
|
+
* @param {string} rawAttribution
|
260
|
+
* @returns {string|undefined}
|
261
|
+
*/
|
262
|
+
function getAttribution(rawAttribution) {
|
263
|
+
// Photographers retain copyright for most CC licenses,
|
264
|
+
// except CC0, so attribution is a bit different
|
265
|
+
return (
|
266
|
+
rawAttribution.match(/\(c\) (.*?),/)?.[1] ||
|
267
|
+
rawAttribution.match(/uploaded by (.*)/)?.[1]
|
268
|
+
);
|
269
|
+
}
|
270
|
+
|
271
|
+
let lastQueryTime = Date.now();
|
272
|
+
/**
|
273
|
+
* @param {URL} url
|
274
|
+
* @returns {Promise<Response|Error>}
|
275
|
+
*/
|
276
|
+
async function getResponse(url) {
|
277
|
+
// If less than one second since last query, delay.
|
278
|
+
const delayTime = 1050 - (Date.now() - lastQueryTime);
|
279
|
+
if (delayTime > 0) {
|
280
|
+
await sleep(delayTime);
|
281
|
+
}
|
282
|
+
|
283
|
+
try {
|
284
|
+
lastQueryTime = Date.now();
|
285
|
+
return await fetch(url);
|
286
|
+
} catch (error) {
|
287
|
+
if (error instanceof Error) {
|
288
|
+
return error;
|
289
|
+
}
|
290
|
+
throw error;
|
291
|
+
}
|
292
|
+
}
|