mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
1153 lines
45 KiB
TypeScript
1153 lines
45 KiB
TypeScript
import JSZip from 'jszip'
|
|
import { getContinentByCountry } from '../../../frontend/src/shared/validators/countryContinents'
|
|
import {
|
|
mapLocalityToGeologicalContextRow,
|
|
mapLocalityToMeasurementRows,
|
|
type LocalityMeasurementCsvRow,
|
|
} from './dwcArchiveExportLocalities'
|
|
import {
|
|
mapOccurrenceToMeasurementRows,
|
|
mapOccurrenceToOccurrenceRow,
|
|
type DwcOccurrenceKey,
|
|
type OccurrenceCsvRow,
|
|
} from './dwcArchiveExportOccurrences'
|
|
import { buildDwcArchiveZipBuffer, resolveTaxonRank, type MeasurementCsvRow } from './dwcArchiveExport'
|
|
import { writeDwcCsvString } from './utils/dwcCsv'
|
|
import {
|
|
DATASET_CREATOR,
|
|
DATASET_DOI,
|
|
DATASET_LICENSE_TITLE,
|
|
DATASET_LICENSE_URL,
|
|
DATASET_NAME,
|
|
DATASET_TITLE,
|
|
DATASET_VERSION,
|
|
MISSING_VALUE,
|
|
} from './dwcMetadata'
|
|
|
|
const isMeaningfulString = (value: unknown): value is string => {
|
|
if (typeof value !== 'string') return false
|
|
const trimmed = value.trim()
|
|
if (!trimmed) return false
|
|
if (trimmed === '-') return false
|
|
return true
|
|
}
|
|
|
|
const toMaybeMeaningful = (value: string | null | undefined): string => (isMeaningfulString(value) ? value.trim() : '')
|
|
|
|
const toMaybeMeaningfulNumber = (value: number | null | undefined): string => {
|
|
if (value === null || value === undefined) return ''
|
|
if (!Number.isFinite(value)) return ''
|
|
if (value === 0) return ''
|
|
return value.toString()
|
|
}
|
|
|
|
const eventIdForLocality = (lid: number): string => `NOW:EVENT:${lid}`
|
|
const locationIdForLocality = (lid: number): string => `NOW:LOC:${lid}`
|
|
const occurrenceIdForRow = (lid: number, speciesId: number): string => `NOW:OCC:${lid}:${speciesId}`
|
|
|
|
type LocalityForDwcDpExport = Parameters<typeof mapLocalityToMeasurementRows>[0]
|
|
type OccurrenceForDwcDpExport = Parameters<typeof mapOccurrenceToOccurrenceRow>[0]
|
|
|
|
const LOOKUP_EXPORT_CHUNK_SIZE = 1000
|
|
|
|
const chunk = <T>(values: T[], size: number): T[][] => {
|
|
const chunks: T[][] = []
|
|
for (let index = 0; index < values.length; index += size) {
|
|
chunks.push(values.slice(index, index + size))
|
|
}
|
|
return chunks
|
|
}
|
|
|
|
const sortOccurrenceKeys = (occurrenceKeys: DwcOccurrenceKey[]): DwcOccurrenceKey[] => {
|
|
return [...occurrenceKeys].sort((a, b) => a.lid - b.lid || a.speciesId - b.speciesId)
|
|
}
|
|
|
|
export const DWC_DP_EVENT_HEADERS = [
|
|
'eventID',
|
|
'parentEventID',
|
|
'eventType',
|
|
'locationID',
|
|
'locality',
|
|
'continent',
|
|
'country',
|
|
'stateProvince',
|
|
'county',
|
|
'higherGeography',
|
|
'decimalLatitude',
|
|
'decimalLongitude',
|
|
'verbatimLatitude',
|
|
'verbatimLongitude',
|
|
'verbatimElevation',
|
|
'eventRemarks',
|
|
'geologicalContextID',
|
|
] as const
|
|
|
|
type DwcDpEventHeader = (typeof DWC_DP_EVENT_HEADERS)[number]
|
|
type DwcDpEventRow = Record<DwcDpEventHeader, string>
|
|
|
|
export const DWC_DP_GEOLOGICAL_CONTEXT_HEADERS = [
|
|
'geologicalContextID',
|
|
'lithostratigraphicTerms',
|
|
'group',
|
|
'formation',
|
|
'member',
|
|
'bed',
|
|
'earliestAgeOrLowestStage',
|
|
'latestAgeOrHighestStage',
|
|
] as const
|
|
|
|
type DwcDpGeologicalContextHeader = (typeof DWC_DP_GEOLOGICAL_CONTEXT_HEADERS)[number]
|
|
type DwcDpGeologicalContextRow = Record<DwcDpGeologicalContextHeader, string>
|
|
|
|
export const DWC_DP_OCCURRENCE_HEADERS = [
|
|
'occurrenceID',
|
|
'eventID',
|
|
'organismQuantity',
|
|
'organismQuantityType',
|
|
'occurrenceStatus',
|
|
'occurrenceRemarks',
|
|
'taxonID',
|
|
'scientificName',
|
|
'scientificNameAuthorship',
|
|
'taxonRank',
|
|
'identificationVerificationStatus',
|
|
] as const
|
|
|
|
type DwcDpOccurrenceHeader = (typeof DWC_DP_OCCURRENCE_HEADERS)[number]
|
|
type DwcDpOccurrenceRow = Record<DwcDpOccurrenceHeader, string>
|
|
|
|
const ASSERTION_HEADERS = [
|
|
'assertionID',
|
|
'verbatimAssertionType',
|
|
'assertionType',
|
|
'assertionTypeIRI',
|
|
'assertionTypeSource',
|
|
'assertionMadeDate',
|
|
'assertionEffectiveDate',
|
|
'assertionValue',
|
|
'assertionValueIRI',
|
|
'assertionValueSource',
|
|
'assertionValueNumeric',
|
|
'assertionUnit',
|
|
'assertionUnitIRI',
|
|
'assertionUnitSource',
|
|
'assertionError',
|
|
'assertionBy',
|
|
'assertionByID',
|
|
'assertionProtocols',
|
|
'assertionProtocolID',
|
|
'assertionReferences',
|
|
'assertionRemarks',
|
|
] as const
|
|
|
|
const DWC_DP_EVENT_ASSERTION_HEADERS = ['eventID', ...ASSERTION_HEADERS] as const
|
|
const DWC_DP_OCCURRENCE_ASSERTION_HEADERS = ['occurrenceID', ...ASSERTION_HEADERS] as const
|
|
|
|
type AssertionHeader = (typeof ASSERTION_HEADERS)[number]
|
|
type AssertionColumns = Record<AssertionHeader, string>
|
|
type DwcDpEventAssertionHeader = (typeof DWC_DP_EVENT_ASSERTION_HEADERS)[number]
|
|
type DwcDpOccurrenceAssertionHeader = (typeof DWC_DP_OCCURRENCE_ASSERTION_HEADERS)[number]
|
|
type DwcDpEventAssertionRow = Record<DwcDpEventAssertionHeader, string>
|
|
type DwcDpOccurrenceAssertionRow = Record<DwcDpOccurrenceAssertionHeader, string>
|
|
|
|
export const DWC_DP_TABLES = {
|
|
event: 'event.csv',
|
|
geologicalContext: 'geological-context.csv',
|
|
occurrence: 'occurrence.csv',
|
|
eventAssertion: 'event-assertion.csv',
|
|
occurrenceAssertion: 'occurrence-assertion.csv',
|
|
dataPackage: 'datapackage.json',
|
|
eml: 'eml.xml',
|
|
} as const
|
|
|
|
const numericValue = (value: string): string => {
|
|
if (!value.trim()) return ''
|
|
const parsed = Number(value)
|
|
return Number.isFinite(parsed) ? value : ''
|
|
}
|
|
|
|
const assertionColumnsFromMeasurement = ({
|
|
measurementID,
|
|
verbatimMeasurementType,
|
|
measurementType,
|
|
measurementValue,
|
|
measurementUnit,
|
|
measurementMethod,
|
|
}: Pick<
|
|
MeasurementCsvRow | LocalityMeasurementCsvRow,
|
|
| 'measurementID'
|
|
| 'verbatimMeasurementType'
|
|
| 'measurementType'
|
|
| 'measurementValue'
|
|
| 'measurementUnit'
|
|
| 'measurementMethod'
|
|
>): AssertionColumns => ({
|
|
assertionID: measurementID,
|
|
verbatimAssertionType: verbatimMeasurementType,
|
|
assertionType: measurementType,
|
|
assertionTypeIRI: '',
|
|
assertionTypeSource: '',
|
|
assertionMadeDate: '',
|
|
assertionEffectiveDate: '',
|
|
assertionValue: measurementValue,
|
|
assertionValueIRI: '',
|
|
assertionValueSource: '',
|
|
assertionValueNumeric: numericValue(measurementValue),
|
|
assertionUnit: measurementUnit,
|
|
assertionUnitIRI: '',
|
|
assertionUnitSource: '',
|
|
assertionError: '',
|
|
assertionBy: '',
|
|
assertionByID: '',
|
|
assertionProtocols: measurementMethod,
|
|
assertionProtocolID: '',
|
|
assertionReferences: '',
|
|
assertionRemarks: '',
|
|
})
|
|
|
|
export const mapLocalityToDwcDpEventRow = (locality: LocalityForDwcDpExport): DwcDpEventRow => {
|
|
const continent = getContinentByCountry(locality.country) ?? ''
|
|
const higherGeography = [
|
|
continent,
|
|
toMaybeMeaningful(locality.country),
|
|
toMaybeMeaningful(locality.state),
|
|
toMaybeMeaningful(locality.county),
|
|
toMaybeMeaningful(locality.basin),
|
|
toMaybeMeaningful(locality.subbasin),
|
|
]
|
|
.filter(Boolean)
|
|
.join('|')
|
|
|
|
return {
|
|
eventID: eventIdForLocality(locality.lid),
|
|
parentEventID: '',
|
|
eventType: 'paleontological locality',
|
|
locationID: locationIdForLocality(locality.lid),
|
|
locality: toMaybeMeaningful(locality.loc_name),
|
|
continent,
|
|
country: toMaybeMeaningful(locality.country),
|
|
stateProvince: toMaybeMeaningful(locality.state),
|
|
county: toMaybeMeaningful(locality.county),
|
|
higherGeography,
|
|
decimalLatitude: toMaybeMeaningfulNumber(locality.dec_lat),
|
|
decimalLongitude: toMaybeMeaningfulNumber(locality.dec_long),
|
|
verbatimLatitude: toMaybeMeaningful(locality.dms_lat),
|
|
verbatimLongitude: toMaybeMeaningful(locality.dms_long),
|
|
verbatimElevation: locality.altitude === null || locality.altitude === undefined ? '' : String(locality.altitude),
|
|
eventRemarks: [
|
|
toMaybeMeaningful(locality.loc_detail),
|
|
toMaybeMeaningful(locality.age_comm),
|
|
toMaybeMeaningful(locality.tax_comm),
|
|
]
|
|
.filter(Boolean)
|
|
.join(' | '),
|
|
geologicalContextID: `NOW:GEO:${locality.lid}`,
|
|
}
|
|
}
|
|
|
|
export const mapLocalityToDwcDpGeologicalContextRow = (locality: LocalityForDwcDpExport): DwcDpGeologicalContextRow => {
|
|
const geologicalContextRow = mapLocalityToGeologicalContextRow(locality)
|
|
|
|
return {
|
|
geologicalContextID: `NOW:GEO:${locality.lid}`,
|
|
lithostratigraphicTerms: geologicalContextRow.lithostratigraphicTerms,
|
|
group: geologicalContextRow.group,
|
|
formation: geologicalContextRow.formation,
|
|
member: geologicalContextRow.member,
|
|
bed: geologicalContextRow.bed,
|
|
earliestAgeOrLowestStage: geologicalContextRow.earliestAgeOrLowestStage,
|
|
latestAgeOrHighestStage: geologicalContextRow.latestAgeOrHighestStage,
|
|
}
|
|
}
|
|
|
|
export const mapOccurrenceToDwcDpOccurrenceRow = (occurrence: OccurrenceForDwcDpExport): DwcDpOccurrenceRow => {
|
|
const occurrenceRow: OccurrenceCsvRow = mapOccurrenceToOccurrenceRow(occurrence)
|
|
const subfamilyRaw = toMaybeMeaningful(occurrence.com_species.subfamily_name)
|
|
const subfamily = subfamilyRaw && subfamilyRaw.toLowerCase().endsWith('inae') ? subfamilyRaw : ''
|
|
const tribe = subfamilyRaw && subfamilyRaw.toLowerCase().endsWith('ini') ? subfamilyRaw : ''
|
|
const subtribe = subfamilyRaw && subfamilyRaw.toLowerCase().endsWith('ina') ? subfamilyRaw : ''
|
|
|
|
return {
|
|
occurrenceID: occurrenceRow.occurrenceID,
|
|
eventID: eventIdForLocality(occurrence.lid),
|
|
organismQuantity: occurrenceRow.organismQuantity,
|
|
organismQuantityType: occurrenceRow.organismQuantityType,
|
|
occurrenceStatus: occurrenceRow.occurrenceStatus,
|
|
occurrenceRemarks: occurrenceRow.occurrenceRemarks,
|
|
taxonID: occurrenceRow.taxonID,
|
|
scientificName: occurrenceRow.scientificName,
|
|
scientificNameAuthorship: toMaybeMeaningful(occurrence.com_species.sp_author),
|
|
taxonRank: resolveTaxonRank({
|
|
family: toMaybeMeaningful(occurrence.com_species.family_name),
|
|
genus: toMaybeMeaningful(occurrence.com_species.genus_name),
|
|
specificEpithet: toMaybeMeaningful(occurrence.com_species.species_name),
|
|
uniqueIdentifier: toMaybeMeaningful(occurrence.com_species.unique_identifier) || null,
|
|
subclassOrSuperorderName: occurrence.com_species.subclass_or_superorder_name,
|
|
subfamily,
|
|
tribe,
|
|
subtribe,
|
|
}),
|
|
identificationVerificationStatus: occurrenceRow.identificationQualifier,
|
|
}
|
|
}
|
|
|
|
export const mapLocalityToDwcDpEventAssertionRows = (locality: LocalityForDwcDpExport): DwcDpEventAssertionRow[] => {
|
|
const eventID = eventIdForLocality(locality.lid)
|
|
|
|
return mapLocalityToMeasurementRows(locality).map(row => ({
|
|
eventID,
|
|
...assertionColumnsFromMeasurement({
|
|
...row,
|
|
measurementID: row.measurementID.replace(locationIdForLocality(locality.lid), eventID),
|
|
}),
|
|
}))
|
|
}
|
|
|
|
export const mapOccurrenceToDwcDpOccurrenceAssertionRows = (
|
|
occurrence: OccurrenceForDwcDpExport
|
|
): DwcDpOccurrenceAssertionRow[] => {
|
|
const occurrenceID = occurrenceIdForRow(occurrence.lid, occurrence.species_id)
|
|
|
|
return mapOccurrenceToMeasurementRows(occurrence).map(row => ({
|
|
occurrenceID,
|
|
...assertionColumnsFromMeasurement(row),
|
|
}))
|
|
}
|
|
|
|
const FIELD_DESCRIPTIONS: Record<string, string> = {
|
|
eventID: 'Stable NOW database event identifier for a paleontological locality.',
|
|
parentEventID: 'Identifier for a containing event, reserved for future event hierarchies.',
|
|
eventType: 'Type of event represented by the row; NOW localities are exported as paleontological locality events.',
|
|
locationID: 'Stable NOW database location identifier for the locality.',
|
|
locality: 'Locality name as curated in the NOW database.',
|
|
continent: 'Continent inferred from the curated country value where possible.',
|
|
country: 'Country or geographic area recorded for the locality.',
|
|
stateProvince: 'State, province, or equivalent administrative subdivision.',
|
|
county: 'County or equivalent lower administrative subdivision.',
|
|
higherGeography: 'Pipe-delimited geographic hierarchy assembled from available NOW locality fields.',
|
|
decimalLatitude:
|
|
'Latitude in decimal degrees. Coordinates may be exact, generalized, rounded, or uncertain depending on source data.',
|
|
decimalLongitude:
|
|
'Longitude in decimal degrees. Coordinates may be exact, generalized, rounded, or uncertain depending on source data.',
|
|
verbatimLatitude: 'Verbatim latitude expression when recorded.',
|
|
verbatimLongitude: 'Verbatim longitude expression when recorded.',
|
|
verbatimElevation: 'Verbatim or numeric elevation value from the curated locality record.',
|
|
eventRemarks: 'Combined locality, age, and taxonomic remarks from curated NOW fields.',
|
|
geologicalContextID: 'Stable identifier for the geological context associated with the locality event.',
|
|
lithostratigraphicTerms:
|
|
'Combined lithostratigraphic terminology from source-publication wording, standardized chronostratigraphic concepts, and NOW harmonization practices.',
|
|
group: 'Lithostratigraphic group name where recorded.',
|
|
formation: 'Lithostratigraphic formation name where recorded.',
|
|
member: 'Lithostratigraphic member name where recorded.',
|
|
bed: 'Lithostratigraphic bed name where recorded.',
|
|
earliestAgeOrLowestStage: 'Earliest age or lowest chronostratigraphic stage associated with the locality.',
|
|
latestAgeOrHighestStage: 'Latest age or highest chronostratigraphic stage associated with the locality.',
|
|
occurrenceID: 'Stable NOW database occurrence identifier linking a locality event to a taxon record.',
|
|
organismQuantity: 'Quantity or abundance value for the occurrence when recorded.',
|
|
organismQuantityType: 'Type of quantity represented by organismQuantity.',
|
|
occurrenceStatus: 'Presence or absence status for the occurrence.',
|
|
occurrenceRemarks: 'Curated occurrence remarks from NOW locality-species data.',
|
|
taxonID: 'Stable NOW taxon identifier; this joins to dwc-a-taxa/taxon.csv in the full export.',
|
|
scientificName: 'Scientific name assembled from curated NOW taxonomic fields.',
|
|
scientificNameAuthorship: 'Scientific name authorship where curated.',
|
|
taxonRank: 'Taxonomic rank derived from curated NOW taxonomic fields when available.',
|
|
identificationVerificationStatus: 'Curated identification status or qualifier.',
|
|
assertionID: 'Stable assertion identifier derived from the source database field and owning event or occurrence.',
|
|
verbatimAssertionType:
|
|
'Original NOW database field name or curated source category that produced the assertion; approx_coord marks approximate coordinate information.',
|
|
assertionType:
|
|
'Human-readable assertion type. Future exports may add controlled predicate or ontology mappings without changing this column.',
|
|
assertionTypeIRI: 'Placeholder for a future ontology IRI identifying the assertion type.',
|
|
assertionTypeSource: 'Placeholder for the vocabulary or ontology source of assertionTypeIRI.',
|
|
assertionMadeDate: 'Date the assertion was made when recorded; empty values mean not recorded.',
|
|
assertionEffectiveDate:
|
|
'Date or interval to which the assertion applies when recorded; empty values mean not recorded.',
|
|
assertionValue: 'Curated or derived assertion value generated directly from NOW database fields.',
|
|
assertionValueIRI: 'Placeholder for a future ontology IRI identifying the assertion value.',
|
|
assertionValueSource: 'Placeholder for the vocabulary or ontology source of assertionValueIRI.',
|
|
assertionValueNumeric: 'Numeric representation of assertionValue when the value can be parsed as a number.',
|
|
assertionUnit: 'Unit associated with the assertion value when recorded.',
|
|
assertionUnitIRI: 'Placeholder for a future ontology IRI identifying the assertion unit.',
|
|
assertionUnitSource: 'Placeholder for the vocabulary or ontology source of assertionUnitIRI.',
|
|
assertionError: 'Uncertainty or error associated with the assertion when recorded.',
|
|
assertionBy: 'Agent responsible for the assertion when recorded; empty values mean not recorded.',
|
|
assertionByID: 'Identifier for assertionBy, reserved for future agent-table interoperability.',
|
|
assertionProtocols: 'Method, protocol, or source database mapping used to generate the assertion.',
|
|
assertionProtocolID: 'Identifier for a protocol record, reserved for future protocol-table interoperability.',
|
|
assertionReferences: 'Reference identifiers or citations supporting the assertion when recorded.',
|
|
assertionRemarks: 'Additional assertion-level remarks when recorded.',
|
|
}
|
|
|
|
const DWC_TERM_IRIS: Record<string, string> = {
|
|
eventID: 'http://rs.tdwg.org/dwc/terms/eventID',
|
|
parentEventID: 'http://rs.tdwg.org/dwc/terms/parentEventID',
|
|
eventType: 'http://rs.tdwg.org/dwc/terms/eventType',
|
|
locationID: 'http://rs.tdwg.org/dwc/terms/locationID',
|
|
locality: 'http://rs.tdwg.org/dwc/terms/locality',
|
|
continent: 'http://rs.tdwg.org/dwc/terms/continent',
|
|
country: 'http://rs.tdwg.org/dwc/terms/country',
|
|
stateProvince: 'http://rs.tdwg.org/dwc/terms/stateProvince',
|
|
county: 'http://rs.tdwg.org/dwc/terms/county',
|
|
higherGeography: 'http://rs.tdwg.org/dwc/terms/higherGeography',
|
|
decimalLatitude: 'http://rs.tdwg.org/dwc/terms/decimalLatitude',
|
|
decimalLongitude: 'http://rs.tdwg.org/dwc/terms/decimalLongitude',
|
|
verbatimLatitude: 'http://rs.tdwg.org/dwc/terms/verbatimLatitude',
|
|
verbatimLongitude: 'http://rs.tdwg.org/dwc/terms/verbatimLongitude',
|
|
verbatimElevation: 'http://rs.tdwg.org/dwc/terms/verbatimElevation',
|
|
eventRemarks: 'http://rs.tdwg.org/dwc/terms/eventRemarks',
|
|
geologicalContextID: 'http://rs.tdwg.org/dwc/terms/geologicalContextID',
|
|
lithostratigraphicTerms: 'http://rs.tdwg.org/dwc/terms/lithostratigraphicTerms',
|
|
group: 'http://rs.tdwg.org/dwc/terms/group',
|
|
formation: 'http://rs.tdwg.org/dwc/terms/formation',
|
|
member: 'http://rs.tdwg.org/dwc/terms/member',
|
|
bed: 'http://rs.tdwg.org/dwc/terms/bed',
|
|
earliestAgeOrLowestStage: 'http://rs.tdwg.org/dwc/terms/earliestAgeOrLowestStage',
|
|
latestAgeOrHighestStage: 'http://rs.tdwg.org/dwc/terms/latestAgeOrHighestStage',
|
|
occurrenceID: 'http://rs.tdwg.org/dwc/terms/occurrenceID',
|
|
organismQuantity: 'http://rs.tdwg.org/dwc/terms/organismQuantity',
|
|
organismQuantityType: 'http://rs.tdwg.org/dwc/terms/organismQuantityType',
|
|
occurrenceStatus: 'http://rs.tdwg.org/dwc/terms/occurrenceStatus',
|
|
occurrenceRemarks: 'http://rs.tdwg.org/dwc/terms/occurrenceRemarks',
|
|
taxonID: 'http://rs.tdwg.org/dwc/terms/taxonID',
|
|
scientificName: 'http://rs.tdwg.org/dwc/terms/scientificName',
|
|
scientificNameAuthorship: 'http://rs.tdwg.org/dwc/terms/scientificNameAuthorship',
|
|
taxonRank: 'http://rs.tdwg.org/dwc/terms/taxonRank',
|
|
identificationVerificationStatus: 'http://rs.tdwg.org/dwc/terms/identificationVerificationStatus',
|
|
}
|
|
|
|
const field = (name: string, type = 'string') => ({
|
|
name,
|
|
title: name.replace(/([A-Z])/g, ' $1').replace(/^./, first => first.toUpperCase()),
|
|
description: FIELD_DESCRIPTIONS[name] ?? `Curated NOW database value for ${name}.`,
|
|
type,
|
|
format: 'default',
|
|
...(DWC_TERM_IRIS[name] ? { 'dcterms:isVersionOf': DWC_TERM_IRIS[name] } : {}),
|
|
})
|
|
|
|
const schemaFor = ({
|
|
headers,
|
|
primaryKey,
|
|
foreignKeys = [],
|
|
}: {
|
|
headers: readonly string[]
|
|
primaryKey: string | string[]
|
|
foreignKeys?: Array<{
|
|
fields: string
|
|
predicate?: string
|
|
reference: { resource: string; fields: string }
|
|
}>
|
|
}) => ({
|
|
fields: headers.map(header => field(header, header.endsWith('Numeric') ? 'number' : 'string')),
|
|
primaryKey,
|
|
missingValues: [MISSING_VALUE],
|
|
foreignKeys,
|
|
})
|
|
|
|
export const buildDwcDataPackageJson = (publicationDateIso: string): string => {
|
|
const dataPackage = {
|
|
profile: 'http://rs.tdwg.org/dwc-dp/1.0/dwc-dp-profile.json',
|
|
name: DATASET_NAME,
|
|
id: DATASET_DOI,
|
|
title: DATASET_TITLE,
|
|
version: DATASET_VERSION,
|
|
created: publicationDateIso,
|
|
homepage: 'https://nowdatabase.org/',
|
|
contributors: [
|
|
{ title: DATASET_CREATOR, role: 'creator' },
|
|
{ title: DATASET_CREATOR, role: 'publisher' },
|
|
{ title: DATASET_CREATOR, role: 'rightsHolder' },
|
|
],
|
|
licenses: [
|
|
{
|
|
name: 'CC-BY-4.0',
|
|
title: DATASET_LICENSE_TITLE,
|
|
path: DATASET_LICENSE_URL,
|
|
},
|
|
],
|
|
keywords: [
|
|
'Darwin Core',
|
|
'Darwin Core Data Package',
|
|
'Darwin Core Archive',
|
|
'NOW database',
|
|
'paleobiology',
|
|
'paleontology',
|
|
'fossil mammals',
|
|
'Cenozoic',
|
|
'occurrence data',
|
|
'taxon traits',
|
|
],
|
|
citation: `${DATASET_CREATOR}. ${DATASET_TITLE}, version ${DATASET_VERSION}. ${DATASET_DOI}. The DOI describes the NOW database generally rather than a single frozen export version; include the export date when citing a downloaded archive.`,
|
|
description:
|
|
'Production Darwin Core Data Package export from the NOW database for relational event, occurrence, geological context, and assertion data. The NOW database is a continuously curated, globally scoped fossil mammal database with Cenozoic emphasis, spanning approximately the last 66 million years.',
|
|
missingValues: [MISSING_VALUE],
|
|
resources: [
|
|
{
|
|
name: 'event',
|
|
path: DWC_DP_TABLES.event,
|
|
profile: 'tabular-data-resource',
|
|
format: 'csv',
|
|
mediatype: 'text/csv',
|
|
description:
|
|
'Paleontological locality events derived from curated NOW locality records. Event identifiers are stable database IDs and are referenced by occurrence rows.',
|
|
schema: schemaFor({
|
|
headers: DWC_DP_EVENT_HEADERS,
|
|
primaryKey: 'eventID',
|
|
foreignKeys: [
|
|
{
|
|
fields: 'geologicalContextID',
|
|
predicate: 'has geological context',
|
|
reference: { resource: 'geological-context', fields: 'geologicalContextID' },
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
{
|
|
name: 'geological-context',
|
|
path: DWC_DP_TABLES.geologicalContext,
|
|
profile: 'tabular-data-resource',
|
|
format: 'csv',
|
|
mediatype: 'text/csv',
|
|
description:
|
|
'Geological and chronostratigraphic context for NOW locality events, reflecting source-publication terminology, standardized chronostratigraphic concepts, and NOW harmonization practices.',
|
|
schema: schemaFor({
|
|
headers: DWC_DP_GEOLOGICAL_CONTEXT_HEADERS,
|
|
primaryKey: 'geologicalContextID',
|
|
}),
|
|
},
|
|
{
|
|
name: 'occurrence',
|
|
path: DWC_DP_TABLES.occurrence,
|
|
profile: 'tabular-data-resource',
|
|
format: 'csv',
|
|
mediatype: 'text/csv',
|
|
description:
|
|
'Fossil mammal occurrence rows derived from curated NOW locality-species associations. occurrenceID is stable within the export and taxonID joins to the DwC-A taxon archive in the full bundle.',
|
|
schema: schemaFor({
|
|
headers: DWC_DP_OCCURRENCE_HEADERS,
|
|
primaryKey: 'occurrenceID',
|
|
foreignKeys: [
|
|
{
|
|
fields: 'eventID',
|
|
predicate: 'happened during',
|
|
reference: { resource: 'event', fields: 'eventID' },
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
{
|
|
name: 'event-assertion',
|
|
path: DWC_DP_TABLES.eventAssertion,
|
|
profile: 'tabular-data-resource',
|
|
format: 'csv',
|
|
mediatype: 'text/csv',
|
|
description:
|
|
'Provenance-aware curated or derived statements associated with locality events. Assertion values are generated directly from curated NOW database fields whose content originates from expert-curated literature data or opinions; empty provenance fields should be read as not recorded.',
|
|
schema: schemaFor({
|
|
headers: DWC_DP_EVENT_ASSERTION_HEADERS,
|
|
primaryKey: 'assertionID',
|
|
foreignKeys: [
|
|
{
|
|
fields: 'eventID',
|
|
predicate: 'asserts about',
|
|
reference: { resource: 'event', fields: 'eventID' },
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
{
|
|
name: 'occurrence-assertion',
|
|
path: DWC_DP_TABLES.occurrenceAssertion,
|
|
profile: 'tabular-data-resource',
|
|
format: 'csv',
|
|
mediatype: 'text/csv',
|
|
description:
|
|
'Provenance-aware curated or derived statements associated with occurrences. Assertion columns include placeholders for future semantic predicates, ontology IRIs, agents, protocols, and richer provenance structures.',
|
|
schema: schemaFor({
|
|
headers: DWC_DP_OCCURRENCE_ASSERTION_HEADERS,
|
|
primaryKey: 'assertionID',
|
|
foreignKeys: [
|
|
{
|
|
fields: 'occurrenceID',
|
|
predicate: 'asserts about',
|
|
reference: { resource: 'occurrence', fields: 'occurrenceID' },
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
],
|
|
}
|
|
|
|
return `${JSON.stringify(dataPackage, null, 2)}\n`
|
|
}
|
|
|
|
export const buildDwcDataPackageEmlXml = (publicationDateIso: string): string => {
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
<eml:eml
|
|
xmlns:eml="eml://ecoinformatics.org/eml-2.1.1"
|
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
packageId="${DATASET_NAME}-dwc-dp-${DATASET_VERSION}"
|
|
system="nowdatabase"
|
|
xsi:schemaLocation="eml://ecoinformatics.org/eml-2.1.1 https://eml.ecoinformatics.org/eml-2.1.1/eml.xsd"
|
|
>
|
|
<dataset>
|
|
<title>${DATASET_TITLE}</title>
|
|
<creator>
|
|
<organizationName>${DATASET_CREATOR}</organizationName>
|
|
<onlineUrl>https://nowdatabase.org/</onlineUrl>
|
|
</creator>
|
|
<metadataProvider>
|
|
<organizationName>${DATASET_CREATOR}</organizationName>
|
|
<onlineUrl>https://nowdatabase.org/</onlineUrl>
|
|
</metadataProvider>
|
|
<associatedParty>
|
|
<organizationName>${DATASET_CREATOR}</organizationName>
|
|
<role>publisher</role>
|
|
<onlineUrl>https://nowdatabase.org/</onlineUrl>
|
|
</associatedParty>
|
|
<pubDate>${publicationDateIso}</pubDate>
|
|
<language>eng</language>
|
|
<series>NOW database Darwin Core export</series>
|
|
<abstract>
|
|
<para>The NOW database Darwin Core export is a production-quality Darwin Core Data Package for relational event, occurrence, geological context, and assertion data from the New and Old Worlds Database of Fossil Mammals. The NOW database is a continuously curated global fossil mammal database supporting large-scale paleobiological and paleontological research.</para>
|
|
<para>The data are expert curated from literature and community expertise. The database spans approximately the last 66 million years, Cenozoic, while maintaining global coverage. This export is intended primarily for researchers downloading and analyzing data.</para>
|
|
</abstract>
|
|
<keywordSet>
|
|
<keyword>Darwin Core Data Package</keyword>
|
|
<keyword>Darwin Core Archive</keyword>
|
|
<keyword>fossil mammals</keyword>
|
|
<keyword>Cenozoic</keyword>
|
|
<keyword>paleobiology</keyword>
|
|
<keyword>paleontology</keyword>
|
|
<keyword>occurrence data</keyword>
|
|
<keyword>assertions</keyword>
|
|
<keyword>taxon traits</keyword>
|
|
<keywordThesaurus>NOW database export keywords</keywordThesaurus>
|
|
</keywordSet>
|
|
<additionalInfo>
|
|
<para>Recommended citation: ${DATASET_CREATOR}. ${DATASET_TITLE}, version ${DATASET_VERSION}. ${DATASET_DOI}. The DOI describes the NOW database generally rather than a single frozen dataset export version; include the export date (${publicationDateIso}) when citing a downloaded archive.</para>
|
|
<para>Missing values in CSV files are serialized as ${MISSING_VALUE}. Coordinate uncertainty is partially represented through assertions where verbatimAssertionType equals approx_coord.</para>
|
|
<para>Future exports may populate ontology IRI, semantic predicate, agent, protocol, and richer provenance fields while preserving the current relational model and column names wherever possible.</para>
|
|
</additionalInfo>
|
|
<intellectualRights>
|
|
<para>Copyright ${DATASET_CREATOR}. This export is licensed under ${DATASET_LICENSE_TITLE} (${DATASET_LICENSE_URL}). Users may share and adapt the data with appropriate attribution.</para>
|
|
</intellectualRights>
|
|
<distribution>
|
|
<online>
|
|
<url function="information">https://nowdatabase.org/</url>
|
|
</online>
|
|
</distribution>
|
|
<coverage>
|
|
<geographicCoverage>
|
|
<geographicDescription>Global. Locality coordinates may be exact, generalized, rounded, or uncertain, depending on the source publication and curation history.</geographicDescription>
|
|
<boundingCoordinates>
|
|
<westBoundingCoordinate>-180</westBoundingCoordinate>
|
|
<eastBoundingCoordinate>180</eastBoundingCoordinate>
|
|
<northBoundingCoordinate>90</northBoundingCoordinate>
|
|
<southBoundingCoordinate>-90</southBoundingCoordinate>
|
|
</boundingCoordinates>
|
|
</geographicCoverage>
|
|
<temporalCoverage>
|
|
<singleDateTime>
|
|
<alternativeTimeScale>
|
|
<timeScaleName>Geologic time</timeScaleName>
|
|
<timeScaleAgeEstimate>Cenozoic, approximately the last 66 million years</timeScaleAgeEstimate>
|
|
<timeScaleAgeUncertainty>Chronological ranges vary by locality and source publication.</timeScaleAgeUncertainty>
|
|
<timeScaleAgeExplanation>NOW locality ages combine source-publication terminology, standardized chronostratigraphic concepts, and NOW harmonization practices.</timeScaleAgeExplanation>
|
|
</alternativeTimeScale>
|
|
</singleDateTime>
|
|
</temporalCoverage>
|
|
<taxonomicCoverage>
|
|
<generalTaxonomicCoverage>Global fossil mammal occurrences, with associated taxonomic names and selected synthesized taxon-level traits in the companion DwC-A taxon archive.</generalTaxonomicCoverage>
|
|
<taxonomicClassification>
|
|
<taxonRankName>class</taxonRankName>
|
|
<taxonRankValue>Mammalia</taxonRankValue>
|
|
<commonName>mammals</commonName>
|
|
</taxonomicClassification>
|
|
</taxonomicCoverage>
|
|
</coverage>
|
|
<maintenance>
|
|
<description>
|
|
<para>The NOW database is continuously curated. This export represents a production snapshot generated from the live curated database rather than a frozen version-specific dataset associated with the DOI.</para>
|
|
</description>
|
|
<maintenanceUpdateFrequency>continual</maintenanceUpdateFrequency>
|
|
</maintenance>
|
|
<contact>
|
|
<organizationName>${DATASET_CREATOR}</organizationName>
|
|
<onlineUrl>https://nowdatabase.org/</onlineUrl>
|
|
</contact>
|
|
<methods>
|
|
<methodStep>
|
|
<description>
|
|
<para>Locality records are exported as Darwin Core event rows, associated geological context rows, and event-level assertions. Locality-species associations are exported as Darwin Core occurrence rows and occurrence-level assertions.</para>
|
|
</description>
|
|
</methodStep>
|
|
<methodStep>
|
|
<description>
|
|
<para>Assertion tables are aligned with the emerging DwC-DP assertion model. Assertions are provenance-aware curated or derived statements associated with events or occurrences. They are generated directly from curated NOW database fields whose content originates from expert-curated literature data or opinions. When assertion provenance fields are empty, they should primarily be interpreted as not recorded.</para>
|
|
</description>
|
|
</methodStep>
|
|
<methodStep>
|
|
<description>
|
|
<para>Geological and chronostratigraphic terminology uses mixed conventions: source-publication terminology, standardized chronostratigraphic concepts, and NOW harmonization practices.</para>
|
|
</description>
|
|
</methodStep>
|
|
<qualityControl>
|
|
<description>
|
|
<para>NOW data are expert curated from the literature and continuously updated. Stable identifiers in this export are derived from NOW database identifiers and are intended for repeatable joins across the files in the downloaded archive.</para>
|
|
</description>
|
|
</qualityControl>
|
|
</methods>
|
|
<project>
|
|
<title>New and Old Worlds Database of Fossil Mammals</title>
|
|
<personnel>
|
|
<organizationName>${DATASET_CREATOR}</organizationName>
|
|
<role>data curator</role>
|
|
</personnel>
|
|
<abstract>
|
|
<para>The NOW database supports research on Cenozoic mammal evolution, biogeography, environments, and fossil occurrence patterns at global scale.</para>
|
|
</abstract>
|
|
</project>
|
|
</dataset>
|
|
</eml:eml>
|
|
`
|
|
}
|
|
|
|
const localitySelect = {
|
|
lid: true,
|
|
loc_name: true,
|
|
basin: true,
|
|
subbasin: true,
|
|
country: true,
|
|
state: true,
|
|
county: true,
|
|
dec_lat: true,
|
|
dec_long: true,
|
|
dms_lat: true,
|
|
dms_long: true,
|
|
approx_coord: true,
|
|
altitude: true,
|
|
loc_detail: true,
|
|
chron: true,
|
|
lgroup: true,
|
|
formation: true,
|
|
member: true,
|
|
bed: true,
|
|
bfa_max: true,
|
|
bfa_min: true,
|
|
bfa_max_abs: true,
|
|
bfa_min_abs: true,
|
|
frac_max: true,
|
|
frac_min: true,
|
|
max_age: true,
|
|
min_age: true,
|
|
date_meth: true,
|
|
age_comm: true,
|
|
site_area: true,
|
|
gen_loc: true,
|
|
plate: true,
|
|
appr_num_spm: true,
|
|
num_spm: true,
|
|
true_quant: true,
|
|
complete: true,
|
|
num_quad: true,
|
|
rock_type: true,
|
|
rt_adj: true,
|
|
lith_comm: true,
|
|
depo_context1: true,
|
|
depo_context2: true,
|
|
depo_context3: true,
|
|
depo_context4: true,
|
|
depo_comm: true,
|
|
sed_env_1: true,
|
|
sed_env_2: true,
|
|
event_circum: true,
|
|
se_comm: true,
|
|
assem_fm: true,
|
|
transport: true,
|
|
trans_mod: true,
|
|
weath_trmp: true,
|
|
pt_conc: true,
|
|
size_type: true,
|
|
vert_pres: true,
|
|
plant_pres: true,
|
|
invert_pres: true,
|
|
time_rep: true,
|
|
taph_comm: true,
|
|
tax_comm: true,
|
|
datum_plane: true,
|
|
tos: true,
|
|
bos: true,
|
|
loc_status: true,
|
|
hominin_skeletal_remains: true,
|
|
climate_type: true,
|
|
biome: true,
|
|
v_ht: true,
|
|
v_struct: true,
|
|
v_envi_det: true,
|
|
disturb: true,
|
|
nutrients: true,
|
|
water: true,
|
|
seasonality: true,
|
|
seas_intens: true,
|
|
pri_prod: true,
|
|
moisture: true,
|
|
temperature: true,
|
|
estimate_precip: true,
|
|
estimate_temp: true,
|
|
estimate_npp: true,
|
|
pers_woody_cover: true,
|
|
pers_pollen_ap: true,
|
|
pers_pollen_nap: true,
|
|
pers_pollen_other: true,
|
|
stone_tool_cut_marks_on_bones: true,
|
|
bipedal_footprints: true,
|
|
stone_tool_technology: true,
|
|
technological_mode_1: true,
|
|
technological_mode_2: true,
|
|
technological_mode_3: true,
|
|
cultural_stage_1: true,
|
|
cultural_stage_2: true,
|
|
cultural_stage_3: true,
|
|
regional_culture_1: true,
|
|
regional_culture_2: true,
|
|
regional_culture_3: true,
|
|
now_time_unit_now_loc_bfa_maxTonow_time_unit: {
|
|
select: { tu_name: true, tu_display_name: true, rank: true, sequence: true },
|
|
},
|
|
now_time_unit_now_loc_bfa_minTonow_time_unit: {
|
|
select: { tu_name: true, tu_display_name: true, rank: true, sequence: true },
|
|
},
|
|
now_syn_loc: {
|
|
select: { synonym: true },
|
|
},
|
|
now_ss: {
|
|
select: { sed_struct: true },
|
|
},
|
|
now_coll_meth: {
|
|
select: { coll_meth: true },
|
|
},
|
|
now_mus: {
|
|
select: {
|
|
museum: true,
|
|
com_mlist: { select: { institution: true, alt_int_name: true, city: true, state: true, country: true } },
|
|
},
|
|
},
|
|
now_ls: {
|
|
select: {
|
|
com_species: {
|
|
select: { order_name: true, tht: true, genus_name: true },
|
|
},
|
|
},
|
|
},
|
|
} as const
|
|
|
|
const occurrenceSelect = {
|
|
lid: true,
|
|
species_id: true,
|
|
nis: true,
|
|
pct: true,
|
|
quad: true,
|
|
mni: true,
|
|
qua: true,
|
|
id_status: true,
|
|
orig_entry: true,
|
|
source_name: true,
|
|
body_mass: true,
|
|
mesowear: true,
|
|
mw_or_high: true,
|
|
mw_or_low: true,
|
|
mw_cs_sharp: true,
|
|
mw_cs_round: true,
|
|
mw_cs_blunt: true,
|
|
mw_scale_min: true,
|
|
mw_scale_max: true,
|
|
mw_value: true,
|
|
microwear: true,
|
|
dc13_mean: true,
|
|
dc13_n: true,
|
|
dc13_max: true,
|
|
dc13_min: true,
|
|
dc13_stdev: true,
|
|
do18_mean: true,
|
|
do18_n: true,
|
|
do18_max: true,
|
|
do18_min: true,
|
|
do18_stdev: true,
|
|
com_species: {
|
|
select: {
|
|
species_id: true,
|
|
class_name: true,
|
|
subclass_or_superorder_name: true,
|
|
order_name: true,
|
|
suborder_or_superfamily_name: true,
|
|
family_name: true,
|
|
subfamily_name: true,
|
|
genus_name: true,
|
|
species_name: true,
|
|
unique_identifier: true,
|
|
taxonomic_status: true,
|
|
common_name: true,
|
|
sp_author: true,
|
|
sp_comment: true,
|
|
},
|
|
},
|
|
} as const
|
|
|
|
export const buildDwcDataPackageZipBufferFromRows = async ({
|
|
localities,
|
|
occurrences,
|
|
publicationDateIso = new Date().toISOString().slice(0, 10),
|
|
}: {
|
|
localities: LocalityForDwcDpExport[]
|
|
occurrences: OccurrenceForDwcDpExport[]
|
|
publicationDateIso?: string
|
|
}): Promise<Buffer> => {
|
|
const eventRows = localities.map(mapLocalityToDwcDpEventRow)
|
|
const geologicalContextRows = localities.map(mapLocalityToDwcDpGeologicalContextRow)
|
|
const occurrenceRows = occurrences.map(mapOccurrenceToDwcDpOccurrenceRow)
|
|
const eventAssertionRows = localities.flatMap(mapLocalityToDwcDpEventAssertionRows)
|
|
const occurrenceAssertionRows = occurrences.flatMap(mapOccurrenceToDwcDpOccurrenceAssertionRows)
|
|
|
|
const zip = new JSZip()
|
|
zip.file(DWC_DP_TABLES.event, writeDwcCsvString(DWC_DP_EVENT_HEADERS, eventRows))
|
|
zip.file(DWC_DP_TABLES.geologicalContext, writeDwcCsvString(DWC_DP_GEOLOGICAL_CONTEXT_HEADERS, geologicalContextRows))
|
|
zip.file(DWC_DP_TABLES.occurrence, writeDwcCsvString(DWC_DP_OCCURRENCE_HEADERS, occurrenceRows))
|
|
zip.file(DWC_DP_TABLES.eventAssertion, writeDwcCsvString(DWC_DP_EVENT_ASSERTION_HEADERS, eventAssertionRows))
|
|
zip.file(
|
|
DWC_DP_TABLES.occurrenceAssertion,
|
|
writeDwcCsvString(DWC_DP_OCCURRENCE_ASSERTION_HEADERS, occurrenceAssertionRows)
|
|
)
|
|
zip.file(DWC_DP_TABLES.dataPackage, buildDwcDataPackageJson(publicationDateIso))
|
|
zip.file(DWC_DP_TABLES.eml, buildDwcDataPackageEmlXml(publicationDateIso))
|
|
|
|
return await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 6 } })
|
|
}
|
|
|
|
const fetchOccurrencesForDwcDataPackageExport = async (
|
|
occurrenceKeys?: DwcOccurrenceKey[]
|
|
): Promise<OccurrenceForDwcDpExport[]> => {
|
|
if (occurrenceKeys && occurrenceKeys.length === 0) return []
|
|
const { nowDb } = await import('../utils/db')
|
|
|
|
if (occurrenceKeys) {
|
|
const occurrences: OccurrenceForDwcDpExport[] = []
|
|
for (const keys of chunk(sortOccurrenceKeys(occurrenceKeys), LOOKUP_EXPORT_CHUNK_SIZE)) {
|
|
const chunkOccurrences = await nowDb.now_ls.findMany({
|
|
where: {
|
|
OR: keys.map(key => ({
|
|
lid: key.lid,
|
|
species_id: key.speciesId,
|
|
})),
|
|
},
|
|
orderBy: [{ lid: 'asc' }, { species_id: 'asc' }],
|
|
select: occurrenceSelect,
|
|
})
|
|
occurrences.push(...(chunkOccurrences as unknown as OccurrenceForDwcDpExport[]))
|
|
}
|
|
return occurrences
|
|
}
|
|
|
|
const occurrences = await nowDb.now_ls.findMany({
|
|
orderBy: [{ lid: 'asc' }, { species_id: 'asc' }],
|
|
select: occurrenceSelect,
|
|
})
|
|
|
|
return occurrences as unknown as OccurrenceForDwcDpExport[]
|
|
}
|
|
|
|
export const buildDwcDataPackageZipBuffer = async (occurrenceKeys?: DwcOccurrenceKey[]): Promise<Buffer> => {
|
|
const occurrences = await fetchOccurrencesForDwcDataPackageExport(occurrenceKeys)
|
|
return await buildDwcDataPackageZipBufferFromOccurrences(occurrences, Boolean(occurrenceKeys))
|
|
}
|
|
|
|
const buildDwcDataPackageZipBufferFromOccurrences = async (
|
|
occurrences: OccurrenceForDwcDpExport[],
|
|
isFilteredExport: boolean
|
|
): Promise<Buffer> => {
|
|
const { nowDb } = await import('../utils/db')
|
|
const localityIds = isFilteredExport ? [...new Set(occurrences.map(occurrence => occurrence.lid))] : undefined
|
|
const localities = (await nowDb.now_loc.findMany({
|
|
where: isFilteredExport ? { lid: { in: localityIds } } : undefined,
|
|
orderBy: { lid: 'asc' },
|
|
select: localitySelect,
|
|
})) as unknown as LocalityForDwcDpExport[]
|
|
|
|
return await buildDwcDataPackageZipBufferFromRows({
|
|
localities,
|
|
occurrences,
|
|
})
|
|
}
|
|
|
|
const addZipEntriesUnderPrefix = async ({
|
|
sourceZipBuffer,
|
|
targetZip,
|
|
prefix,
|
|
}: {
|
|
sourceZipBuffer: Buffer
|
|
targetZip: JSZip
|
|
prefix: string
|
|
}): Promise<void> => {
|
|
const sourceZip = await JSZip.loadAsync(sourceZipBuffer)
|
|
const files = Object.values(sourceZip.files).filter(file => !file.dir)
|
|
|
|
await Promise.all(
|
|
files.map(async file => {
|
|
targetZip.file(`${prefix}/${file.name}`, await file.async('nodebuffer'))
|
|
})
|
|
)
|
|
}
|
|
|
|
const buildFullDarwinCoreReadme = (): string => `${DATASET_TITLE}
|
|
Version: ${DATASET_VERSION}
|
|
Package name: ${DATASET_NAME}
|
|
Creator / publisher / rights holder: ${DATASET_CREATOR}
|
|
License: ${DATASET_LICENSE_TITLE} (${DATASET_LICENSE_URL})
|
|
Identifier: ${DATASET_DOI}
|
|
|
|
This production export contains two separate standards-based Darwin Core
|
|
artifacts from the NOW database (New and Old Worlds Database of Fossil Mammals).
|
|
The outer ZIP is a convenience bundle, not a single DwC-DP or DwC-A artifact.
|
|
Each subdirectory should be treated as its own standards-based export.
|
|
|
|
Directory tree
|
|
|
|
.
|
|
|-- README.txt
|
|
|-- dwc-dp/
|
|
| |-- datapackage.json
|
|
| |-- eml.xml
|
|
| |-- event.csv
|
|
| |-- geological-context.csv
|
|
| |-- occurrence.csv
|
|
| |-- event-assertion.csv
|
|
| \`-- occurrence-assertion.csv
|
|
\`-- dwc-a-taxa/
|
|
|-- meta.xml
|
|
|-- eml.xml
|
|
|-- taxon.csv
|
|
\`-- measurementorfact.csv
|
|
|
|
Scientific scope
|
|
|
|
The NOW database is a continuously curated global fossil mammal database. The
|
|
export is paleobiological and paleontological in scope, is expert curated from
|
|
literature and community expertise, and supports large-scale research on fossil
|
|
mammal occurrences, taxonomy, traits, environments, and geological context. The
|
|
database spans approximately the last 66 million years, Cenozoic, while
|
|
maintaining global coverage.
|
|
|
|
Relationship between DwC-DP and DwC-A
|
|
|
|
dwc-dp/ is a Darwin Core Data Package for relational event, occurrence,
|
|
geological context, and assertion data. It preserves the locality-to-occurrence
|
|
relationships in a table structure that is easier to analyze than a single
|
|
DwC-A star schema for this part of the database.
|
|
|
|
dwc-a-taxa/ is a Darwin Core Archive for taxonomic records and synthesized
|
|
taxon-level traits. Taxon-level traits remain in DwC-A Taxon +
|
|
MeasurementOrFact form because these values are generated directly from curated
|
|
taxon fields and are not currently linked to individual specimen, material
|
|
sample, event, or occurrence source entities. Keeping these traits in the
|
|
taxon-centered DwC-A preserves compatibility with existing DwC-A tooling and
|
|
existing consumers of the taxon trait export.
|
|
|
|
Join keys
|
|
|
|
- dwc-dp/event.csv eventID joins to dwc-dp/occurrence.csv eventID.
|
|
- dwc-dp/event.csv geologicalContextID joins to
|
|
dwc-dp/geological-context.csv geologicalContextID.
|
|
- dwc-dp/event.csv eventID joins to dwc-dp/event-assertion.csv eventID.
|
|
- dwc-dp/occurrence.csv occurrenceID joins to
|
|
dwc-dp/occurrence-assertion.csv occurrenceID.
|
|
- dwc-dp/occurrence.csv taxonID joins to dwc-a-taxa/taxon.csv taxonID.
|
|
- dwc-a-taxa/taxon.csv taxonID joins to
|
|
dwc-a-taxa/measurementorfact.csv taxonID.
|
|
|
|
Identifier stability
|
|
|
|
Identifiers are stable database IDs derived from NOW database identifiers. They
|
|
are intended to support repeatable joins within and across downloaded exports.
|
|
They should not be interpreted as globally minted persistent identifiers unless
|
|
explicitly documented as such in future releases.
|
|
|
|
Assertions
|
|
|
|
Assertion tables are aligned with the emerging DwC-DP assertion model. They
|
|
represent provenance-aware curated or derived statements associated with events
|
|
or occurrences. Assertions are generated directly from curated database fields
|
|
whose content originates from expert-curated literature data or opinions. Empty
|
|
assertion provenance fields should primarily be interpreted as not recorded.
|
|
|
|
Geological context and coordinates
|
|
|
|
Geological and chronostratigraphic terminology uses mixed conventions:
|
|
source-publication terminology, standardized chronostratigraphic concepts, and
|
|
NOW harmonization practices. Coordinates may be exact, generalized, rounded, or
|
|
uncertain. Coordinate uncertainty is partially represented using assertions
|
|
where verbatimAssertionType is approx_coord.
|
|
|
|
Missing values
|
|
|
|
Missing values in CSV files are serialized as ${MISSING_VALUE}. Data Package
|
|
metadata also declares ${MISSING_VALUE} as the missing value marker. Treat empty
|
|
assertion provenance fields and other missing fields as not recorded unless a
|
|
field-specific description states otherwise.
|
|
|
|
Citation guidance
|
|
|
|
Recommended citation:
|
|
${DATASET_CREATOR}. ${DATASET_TITLE}, version ${DATASET_VERSION}. ${DATASET_DOI}.
|
|
Include the export download or generation date when citing a specific downloaded
|
|
archive.
|
|
|
|
The DOI ${DATASET_DOI} describes the NOW database generally rather than a single
|
|
frozen dataset export version.
|
|
|
|
Future interoperability
|
|
|
|
Several assertion columns are reserved for ontology IRIs, semantic predicates,
|
|
agent identifiers, protocol identifiers, and richer provenance structures. These
|
|
placeholders are included to support future semantic interoperability while
|
|
preserving the current CSV schemas, identifiers, and relational structure.
|
|
`
|
|
|
|
export const buildFullDarwinCoreExportZipBufferFromArchives = async ({
|
|
dwcDataPackageZipBuffer,
|
|
dwcTaxonArchiveZipBuffer,
|
|
}: {
|
|
dwcDataPackageZipBuffer: Buffer
|
|
dwcTaxonArchiveZipBuffer: Buffer
|
|
}): Promise<Buffer> => {
|
|
const zip = new JSZip()
|
|
zip.file('README.txt', buildFullDarwinCoreReadme())
|
|
await addZipEntriesUnderPrefix({
|
|
sourceZipBuffer: dwcDataPackageZipBuffer,
|
|
targetZip: zip,
|
|
prefix: 'dwc-dp',
|
|
})
|
|
await addZipEntriesUnderPrefix({
|
|
sourceZipBuffer: dwcTaxonArchiveZipBuffer,
|
|
targetZip: zip,
|
|
prefix: 'dwc-a-taxa',
|
|
})
|
|
|
|
return await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE', compressionOptions: { level: 6 } })
|
|
}
|
|
|
|
export const buildFullDarwinCoreExportZipBuffer = async (occurrenceKeys?: DwcOccurrenceKey[]): Promise<Buffer> => {
|
|
const occurrences = occurrenceKeys ? await fetchOccurrencesForDwcDataPackageExport(occurrenceKeys) : undefined
|
|
const speciesIds = occurrences ? [...new Set(occurrences.map(occurrence => occurrence.species_id))] : undefined
|
|
const [dwcDataPackageZipBuffer, dwcTaxonArchiveZipBuffer] = await Promise.all([
|
|
occurrences ? buildDwcDataPackageZipBufferFromOccurrences(occurrences, true) : buildDwcDataPackageZipBuffer(),
|
|
buildDwcArchiveZipBuffer(speciesIds),
|
|
])
|
|
|
|
return await buildFullDarwinCoreExportZipBufferFromArchives({
|
|
dwcDataPackageZipBuffer,
|
|
dwcTaxonArchiveZipBuffer,
|
|
})
|
|
}
|
|
|
|
export type DwcDpLocalityFixture = LocalityForDwcDpExport
|
|
export type DwcDpOccurrenceFixture = OccurrenceForDwcDpExport
|