KML export for localities

This commit is contained in:
Tatu Laras
2025-05-26 13:16:18 +03:00
parent aca10b03c2
commit 8b3e393639
4 changed files with 114 additions and 2 deletions
@@ -1,10 +1,11 @@
import { useMemo } from 'react'
import { type MRT_ColumnDef } from 'material-react-table'
import { MRT_TableInstance, type MRT_ColumnDef, MRT_RowData } from 'material-react-table'
import { useGetAllLocalitiesQuery, useGetLocalitySpeciesListMutation } from '../../redux/localityReducer'
import { Locality } from '@/shared/types'
import { TableView } from '../TableView/TableView'
import { useNotify } from '@/hooks/notification'
import { LocalitiesMap } from '../Map/LocalitiesMap'
import { Placemark, generateKml } from '@/util/kml'
const decimalCount = (num: number) => {
const numAsString = num.toString()
@@ -383,6 +384,19 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
a.click()
}
// Downloads a KML file of filtered localities.
const kmlExport = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
const rowData: Locality[] = table.getPrePaginationRowModel().rows.map(row => row.original as unknown as Locality)
const dataString = generateKml(rowData)
const blob = new Blob([dataString], { type: 'text/kml' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'localities.kml'
a.click()
}
const checkRowRestriction = (row: Locality) => {
return !!row.loc_status
}
@@ -401,6 +415,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
data={localitiesQueryData}
url="locality"
combinedExport={combinedExport}
kmlExport={kmlExport}
exportIsLoading={isLoading}
enableColumnFilterModes={true}
/>
@@ -11,12 +11,14 @@ import AddCircleIcon from '@mui/icons-material/AddCircle'
export const TableToolBar = <T extends MRT_RowData>({
table,
combinedExport,
kmlExport,
exportIsLoading,
selectorFn,
showNewButton,
}: {
table: MRT_TableInstance<T>
combinedExport?: (lids: number[]) => Promise<void>
kmlExport?: (table: MRT_TableInstance<T>) => void
exportIsLoading?: boolean
selectorFn?: (id: T) => void
showNewButton?: boolean
@@ -73,7 +75,16 @@ export const TableToolBar = <T extends MRT_RowData>({
>
Export table
</MenuItem>
{kmlExport && (
<MenuItem
onClick={() => {
kmlExport(table)
handleClose()
}}
>
Export KML
</MenuItem>
)}
{combinedExport && (
<Box>
<Tooltip
@@ -8,6 +8,7 @@ import {
useMaterialReactTable,
MaterialReactTable,
MRT_VisibilityState,
MRT_TableInstance,
} from 'material-react-table'
import { Box, CircularProgress, Paper } from '@mui/material'
import { useLocation, useNavigate } from 'react-router-dom'
@@ -36,6 +37,7 @@ export const TableView = <T extends MRT_RowData>({
url,
title,
combinedExport,
kmlExport,
exportIsLoading,
enableColumnFilterModes,
serverSidePagination,
@@ -50,6 +52,7 @@ export const TableView = <T extends MRT_RowData>({
selectorFn?: (id: T) => void
url?: string
combinedExport?: (lids: number[]) => Promise<void>
kmlExport?: (table: MRT_TableInstance<T>) => void
exportIsLoading?: boolean
enableColumnFilterModes?: boolean
serverSidePagination?: boolean
@@ -202,6 +205,7 @@ export const TableView = <T extends MRT_RowData>({
<TableToolBar<T>
table={table}
combinedExport={combinedExport}
kmlExport={kmlExport}
exportIsLoading={exportIsLoading}
showNewButton={editRights.new && title != 'Locality-Species-Cross-Search'}
/>
+82
View File
@@ -0,0 +1,82 @@
import { Locality } from '@/shared/types'
// Replaces special characters with html entities.
const htmlEncode = (str: string) => {
const el = document.createElement('div')
el.innerText = str
return el.innerHTML
}
// A single row in a locality info table
const tableRow = (title: string, value: string, indentLevel: number) => {
const indent = '\t'.repeat(indentLevel)
let tableRowString = `${indent}<tr>\n`
tableRowString += `${indent}\t<td>\n`
tableRowString += `${indent}\t\t<b>${title}</b>\n`
tableRowString += `${indent}\t</td>\n`
tableRowString += `${indent}\t<td>\n`
tableRowString += `${indent}\t\t${value}\n`
tableRowString += `${indent}\t</td>\n`
tableRowString += `${indent}</tr>\n`
return tableRowString
}
// Locality info table that shows up when clicking map marker in Google Earth for example.
const localityInfoTable = (locality: Locality, indentLevel: number = 3) => {
const indent = '\t'.repeat(indentLevel)
let tableString = `${indent}<![CDATA[\n`
tableString += `${indent}<table style="float: none; clear: none; border-collapse: collapse; margin-bottom: 16px; font-size: 90%;">\n`
tableString += tableRow('Locality&nbsp;ID:', locality.lid.toString(), indentLevel + 1)
tableString += tableRow('Country:', htmlEncode(locality.country), indentLevel + 1)
tableString += tableRow('Lat/Long:', `${locality.dms_lat},${locality.dms_long}`, indentLevel + 1)
tableString += tableRow(
'Age:',
`${locality.max_age} Ma (${locality.bfa_max}) - ${locality.min_age} Ma (${locality.bfa_min})`,
indentLevel + 1
)
tableString += `${indent}</table>\n`
tableString += `${indent}]]>\n`
return tableString
}
const generatePlacemarkKml = (locality: Locality) => {
let kmlString = `\t<Placemark>\n`
kmlString += `\t\t<name>${htmlEncode(locality.loc_name)}</name>\n`
kmlString += `\t\t<description>\n`
kmlString += localityInfoTable(locality)
kmlString += `\t\t</description>\n`
kmlString += `\t\t<Point>\n`
kmlString += `\t\t\t<coordinates>${locality.dec_long},${locality.dec_lat},0</coordinates>\n`
kmlString += `\t\t</Point>\n`
kmlString += `\t</Placemark>\n`
return kmlString
}
// Returns an XML / KML string containing locality information as map markers, openable in Google Earth for example.
export const generateKml = (localities: Locality[]) => {
let kmlString = `<?xml version="1.0" encoding="UTF-8"?>\n`
kmlString +=
`<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" ` +
`xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">\n`
kmlString += `<Document>\n`
kmlString += `\t<name>NOW database ${new Date().toLocaleString()}</name>\n`
kmlString += localities.map(generatePlacemarkKml).join('')
kmlString += `</Document>\n</kml>`
return kmlString
}