Add occurrence table map exports

This commit is contained in:
karilint
2026-05-19 16:14:49 +03:00
parent fd83afdd1a
commit 969287e2c6
3 changed files with 112 additions and 3 deletions
@@ -1,5 +1,5 @@
import { useMemo } from 'react'
import { type MRT_ColumnDef } from 'material-react-table'
import { type MRT_ColumnDef, type MRT_RowData, type MRT_TableInstance } from 'material-react-table'
import { CrossSearch, formatDevelopmentalCrownType } from '@/shared/types'
import { TableView } from '../TableView/TableView'
import type { ColumnVisibilityGroup } from '../TableView/TableToolBar'
@@ -12,6 +12,9 @@ import { OccurrenceDwcExportMenuItem } from '@/components/Occurrence/OccurrenceD
import { OccurrenceDwcDpExportMenuItem } from '@/components/Occurrence/OccurrenceDwcDpExportMenuItem'
import { OccurrenceFullDarwinCoreExportMenuItem } from '@/components/Occurrence/OccurrenceFullDarwinCoreExportMenuItem'
import { matchesCountryOrContinent } from '@/shared/validators/countryContinents'
import { generateKml } from '@/util/kml'
import { currentDateAsString } from '@/shared/currentDateAsString'
import { getUniqueCrossSearchMapExportLocalities } from '@/components/Species/localitySpeciesMapExport'
export const CrossSearchTable = ({ selectorFn }: { selectorFn?: (newObject: CrossSearch) => void }) => {
const { sqlLimit, sqlOffset, sqlColumnFilters, sqlOrderBy } = usePageContext()
@@ -1072,6 +1075,32 @@ export const CrossSearchTable = ({ selectorFn }: { selectorFn?: (newObject: Cros
return !!row.loc_status
}
const getExportLocalities = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
const rows = table.getPrePaginationRowModel().rows.map(row => row.original as unknown as CrossSearch)
return getUniqueCrossSearchMapExportLocalities(rows)
}
const kmlExport = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
const dataString = generateKml(getExportLocalities(table))
const blob = new Blob([dataString], { type: 'text/kml' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `occurrences-${currentDateAsString()}.kml`
a.click()
}
const svgExport = async <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
const { generateSvg } = await import('@/components/Map/generateSvg')
const dataString = generateSvg(getExportLocalities(table))
const blob = new Blob([dataString], { type: 'image/svg+xml' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `occurrences-map-${currentDateAsString()}.svg`
a.click()
}
return (
<>
<LocalitiesMap localities={localitiesData} isFetching={localitiesFetching || isFetching} />
@@ -1090,6 +1119,8 @@ export const CrossSearchTable = ({ selectorFn }: { selectorFn?: (newObject: Cros
enableColumnFilterModes={true}
serverSidePagination={true}
isCrossSearchTable={true}
kmlExport={kmlExport}
svgExport={svgExport}
isError={isError}
error={error}
renderExtraExportMenuItems={handleClose => (
@@ -1,4 +1,4 @@
import type { Locality, LocalityDetailsType, LocalitySpecies, SpeciesLocality } from '@/shared/types'
import type { CrossSearch, Locality, LocalityDetailsType, LocalitySpecies, SpeciesLocality } from '@/shared/types'
type SpeciesSource = {
species_id?: number | null
@@ -121,3 +121,38 @@ export const getUniqueOccurrenceMapExportLocalities = (
return [exportLocality]
}
export const getUniqueCrossSearchMapExportLocalities = (rows: CrossSearch[]): MapExportLocality[] => {
const localitiesById = new Map<number, MapExportLocality>()
rows.forEach(row => {
const locality = toMapExportLocality({
lid: row.lid_now_loc,
loc_name: row.loc_name,
country: row.country,
dms_lat: row.dms_lat,
dms_long: row.dms_long,
dec_lat: row.dec_lat,
dec_long: row.dec_long,
bfa_max: row.bfa_max,
bfa_min: row.bfa_min,
max_age: row.max_age,
min_age: row.min_age,
altitude: row.altitude,
appr_num_spm: row.appr_num_spm,
})
if (!locality) return
const exportLocality = localitiesById.get(locality.lid) ?? locality
addSpecies(exportLocality, {
species_id: row.species_id_com_species,
genus_name: row.genus_name,
species_name: row.species_name,
unique_identifier: row.unique_identifier,
})
localitiesById.set(locality.lid, exportLocality)
})
return [...localitiesById.values()]
}
@@ -1,13 +1,15 @@
import { describe, expect, it } from '@jest/globals'
import {
getUniqueCrossSearchMapExportLocalities,
getUniqueOccurrenceMapExportLocalities,
getUniqueSpeciesLocalityMapExportLocalities,
toMapExportLocality,
} from '@/components/Species/localitySpeciesMapExport'
import { generateKml } from '@/util/kml'
import type { LocalityDetailsType, LocalitySpecies, SpeciesLocality } from '@/shared/types'
import type { CrossSearch, LocalityDetailsType, LocalitySpecies, SpeciesLocality } from '@/shared/types'
const crossSearch = (overrides: unknown): CrossSearch => overrides as CrossSearch
const speciesLocality = (overrides: unknown): SpeciesLocality => overrides as SpeciesLocality
const localitySpecies = (overrides: unknown): LocalitySpecies => overrides as LocalitySpecies
const locality = (overrides: unknown): LocalityDetailsType => overrides as LocalityDetailsType
@@ -103,6 +105,47 @@ describe('locality species map export helpers', () => {
expect(result[0]?.species).toEqual(['Ursus arctos', 'Vulpes vulpes'])
})
it('groups cross-search occurrence rows by locality with species names', () => {
const result = getUniqueCrossSearchMapExportLocalities([
crossSearch({
lid_now_loc: 1,
loc_name: 'A',
country: 'Finland',
dec_lat: 1,
dec_long: 2,
species_id_com_species: 100,
genus_name: 'Ursus',
species_name: 'arctos',
unique_identifier: '-',
}),
crossSearch({
lid_now_loc: 1,
loc_name: 'A',
country: 'Finland',
dec_lat: 1,
dec_long: 2,
species_id_com_species: 101,
genus_name: 'Vulpes',
species_name: 'vulpes',
unique_identifier: null,
}),
crossSearch({
lid_now_loc: 2,
loc_name: 'B',
country: 'Sweden',
dec_lat: null,
dec_long: 2,
species_id_com_species: 102,
genus_name: 'Alces',
species_name: 'alces',
}),
])
expect(result).toHaveLength(1)
expect(result[0]).toMatchObject({ lid: 1, loc_name: 'A', country: 'Finland' })
expect(result[0]?.species).toEqual(['Ursus arctos', 'Vulpes vulpes'])
})
it('writes species lists into KML descriptions when provided', () => {
const exportLocality = toMapExportLocality({
lid: 1,