mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
SVG export functionality
This commit is contained in:
@@ -6,6 +6,7 @@ import { TableView } from '../TableView/TableView'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { LocalitiesMap } from '../Map/LocalitiesMap'
|
||||
import { generateKml } from '@/util/kml'
|
||||
import { generateSvg } from '../Map/generateSvg'
|
||||
|
||||
const decimalCount = (num: number) => {
|
||||
const numAsString = num.toString()
|
||||
@@ -397,6 +398,17 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
a.click()
|
||||
}
|
||||
|
||||
const svgExport = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
|
||||
const rowData: Locality[] = table.getPrePaginationRowModel().rows.map(row => row.original as unknown as Locality)
|
||||
const dataString = generateSvg(rowData)
|
||||
const blob = new Blob([dataString], { type: 'image/svg+xml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'map.svg'
|
||||
a.click()
|
||||
}
|
||||
|
||||
const checkRowRestriction = (row: Locality) => {
|
||||
return !!row.loc_status
|
||||
}
|
||||
@@ -416,6 +428,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
url="locality"
|
||||
combinedExport={combinedExport}
|
||||
kmlExport={kmlExport}
|
||||
svgExport={svgExport}
|
||||
exportIsLoading={isLoading}
|
||||
enableColumnFilterModes={true}
|
||||
/>
|
||||
|
||||
@@ -131,7 +131,7 @@ export const LocalitiesMap = ({ localitiesQueryData, localitiesQueryIsFetching }
|
||||
<div ref={mapRef}></div>
|
||||
</div>
|
||||
|
||||
{!localitiesQueryIsFetching && (
|
||||
{!localitiesQueryIsFetching && localitiesQueryData && (
|
||||
<div className="button-row">
|
||||
<Button variant="contained" startIcon={<MapIcon />} onClick={() => setIsOpen(v => !v)}>
|
||||
{isOpen ? 'Close' : 'Open'} map
|
||||
|
||||
@@ -1,29 +1,61 @@
|
||||
import { SVG } from '@svgdotjs/svg.js'
|
||||
import { borders } from './country_borders_WGS84'
|
||||
import { Locality } from '@/shared/types'
|
||||
|
||||
type CoordinateConversion = 'web_mercanter'
|
||||
interface PixelCoordinates {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
const webMercanterConvert = (lat: number, long: number): PixelCoordinates => {
|
||||
const zoomLevel = 2
|
||||
// Converts decimal latitude longitude (degrees) coordinates to pixel coordinates,
|
||||
// using width as the total map width.
|
||||
const webMercatorConvert = (lat: number, long: number, width: number): PixelCoordinates => {
|
||||
const pi = 3.141592653589793
|
||||
const latRadians = lat * (pi / 180)
|
||||
const longRadians = long * (pi / 180)
|
||||
|
||||
return {
|
||||
x: (1 / (pi * 2)) * (2 ^ zoomLevel) * (pi + long),
|
||||
y: (1 / (pi * 2)) * (2 ^ zoomLevel) * (pi - Math.log(Math.tan(pi / 4 + lat / 2))),
|
||||
x: (1 / (pi * 2)) * (pi + longRadians) * width,
|
||||
y: (1 / (pi * 2)) * (pi - Math.log(Math.tan(pi / 4 + latRadians / 2))) * width,
|
||||
}
|
||||
}
|
||||
|
||||
export const generateSvg = (conversion: CoordinateConversion) => {
|
||||
// Generates and returns an SVG image of localities on a map.
|
||||
export const generateSvg = (
|
||||
localities: Locality[],
|
||||
mapBorderColor = '#000000',
|
||||
mapFillColor = '#ffffff',
|
||||
markerBorderColor = '#ff0000',
|
||||
markerFillColor = '#ffffff',
|
||||
transparent = true
|
||||
) => {
|
||||
// Generate picture
|
||||
const draw = SVG()
|
||||
const polygon = draw.polygon('0,0 100,50 50,100').fill('none').stroke({ width: 1 })
|
||||
|
||||
const dataString = draw.svg()
|
||||
const blob = new Blob([dataString], { type: 'text/kml' })
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'map.svg'
|
||||
a.click()
|
||||
const width = 2048
|
||||
draw.viewbox(0, 0, width, width)
|
||||
|
||||
if (!transparent) draw.polygon(`0,0 ${width},0 ${width},${width} 0,${width}`).fill('#ffffff')
|
||||
|
||||
borders.forEach(country_border => {
|
||||
const points: string[] = []
|
||||
|
||||
country_border.forEach(coordinate => {
|
||||
const pixelCoords = webMercatorConvert(coordinate[0], coordinate[1], width)
|
||||
points.push(`${pixelCoords.x},${pixelCoords.y}`)
|
||||
})
|
||||
|
||||
const polygon = points.join(' ')
|
||||
draw.polygon(polygon).stroke({ width: 1, color: mapBorderColor }).fill(mapFillColor)
|
||||
})
|
||||
|
||||
localities.forEach(locality => {
|
||||
const pixelCoords = webMercatorConvert(locality.dec_lat, locality.dec_long, width)
|
||||
const radius = 3
|
||||
const topLeftX = pixelCoords.x - radius
|
||||
const topLeftY = pixelCoords.y - radius
|
||||
draw.circle(radius).move(topLeftX, topLeftY).stroke({ width: 1, color: markerBorderColor }).fill(markerFillColor)
|
||||
})
|
||||
|
||||
return draw.svg()
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
table,
|
||||
combinedExport,
|
||||
kmlExport,
|
||||
svgExport,
|
||||
exportIsLoading,
|
||||
isCrossSearchTable,
|
||||
selectorFn,
|
||||
@@ -21,6 +22,7 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
table: MRT_TableInstance<T>
|
||||
combinedExport?: (lids: number[]) => Promise<void>
|
||||
kmlExport?: (table: MRT_TableInstance<T>) => void
|
||||
svgExport?: (table: MRT_TableInstance<T>) => void
|
||||
exportIsLoading?: boolean
|
||||
isCrossSearchTable?: boolean
|
||||
selectorFn?: (id: T) => void
|
||||
@@ -93,6 +95,18 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
Export KML
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{svgExport && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
svgExport(table)
|
||||
handleClose()
|
||||
}}
|
||||
>
|
||||
Export SVG map
|
||||
</MenuItem>
|
||||
)}
|
||||
|
||||
{combinedExport && (
|
||||
<Box>
|
||||
<Tooltip
|
||||
|
||||
@@ -39,6 +39,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
title,
|
||||
combinedExport,
|
||||
kmlExport,
|
||||
svgExport,
|
||||
exportIsLoading,
|
||||
isCrossSearchTable,
|
||||
enableColumnFilterModes,
|
||||
@@ -55,6 +56,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
url?: string
|
||||
combinedExport?: (lids: number[]) => Promise<void>
|
||||
kmlExport?: (table: MRT_TableInstance<T>) => void
|
||||
svgExport?: (table: MRT_TableInstance<T>) => void
|
||||
exportIsLoading?: boolean
|
||||
isCrossSearchTable?: boolean
|
||||
enableColumnFilterModes?: boolean
|
||||
@@ -207,6 +209,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
table={table}
|
||||
combinedExport={combinedExport}
|
||||
kmlExport={kmlExport}
|
||||
svgExport={svgExport}
|
||||
exportIsLoading={exportIsLoading}
|
||||
isCrossSearchTable={isCrossSearchTable}
|
||||
showNewButton={editRights.new && title != 'Locality-Species-Cross-Search'}
|
||||
|
||||
Reference in New Issue
Block a user