Map testing, custom table based on Tanstack Table

This commit is contained in:
cracklesparkle
2024-10-25 10:02:40 +09:00
parent edb6ae00fb
commit 115c6ec417
13 changed files with 1956 additions and 125 deletions

View File

@ -0,0 +1,51 @@
.resize_handler {
position: absolute;
opacity: 0;
top: 0;
right: 0;
height: 100%;
width: 5px;
background: #27bbff;
cursor: col-resize;
user-select: none;
touch-action: none;
border-radius: 6px;
}
.resize_handler:hover {
opacity: 1;
}
.tr {
display: flex;
//width: 100%;
//max-width: 100%;
width: fit-content;
}
.th {
position: relative;
}
.th,
.td {
display: flex;
width: auto;
}
.thead {
display: flex;
width: 100%;
}
.table {
display: flex;
flex-direction: column;
width: 100%;
}
.tbody {
display: flex;
flex-direction: column;
width: 100%;
}

View File

@ -0,0 +1,102 @@
import { Button, Flex, Input, Table } from '@mantine/core';
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from '@tanstack/react-table';
import { useMemo, useState } from 'react';
import styles from './CustomTable.module.scss'
// Sample data
const initialData = [
{ id: 1, name: 'John Doe', age: 25 },
{ id: 2, name: 'Jane Smith', age: 30 },
{ id: 3, name: 'Sam Green', age: 22 },
];
// Define columns
const columns: ColumnDef<typeof initialData[0]>[] = [
{
accessorKey: 'name',
header: 'Name',
cell: (info: any) => info.getValue(),
maxSize: Number.MAX_SAFE_INTEGER,
},
{
accessorKey: 'age',
header: 'Age',
cell: (info: any) => info.getValue(),
},
];
const CustomTable = () => {
const [data, setData] = useState(initialData);
const [editingCell, setEditingCell] = useState({ rowIndex: null, columnId: null });
const tableColumns = useMemo<ColumnDef<typeof data[0]>[]>(() => columns, []);
const table = useReactTable({
data,
columns: tableColumns,
getCoreRowModel: getCoreRowModel(),
columnResizeMode: "onChange",
});
// Function to handle cell edit
const handleEditCell = (rowIndex: any, columnId: any, value: any) => {
const updatedData = [...data];
updatedData[rowIndex][columnId] = value;
setData(updatedData);
//setEditingCell({ rowIndex: null, columnId: null });
};
return (
<Table striped withColumnBorders highlightOnHover className={styles.table}>
<Table.Thead className={styles.thead}>
{table.getHeaderGroups().map(headerGroup => (
<Table.Tr key={headerGroup.id} className={styles.tr}>
{headerGroup.headers.map((header) => (
<Table.Th key={header.id} className={styles.th} w={header.getSize()}>
{flexRender(header.column.columnDef.header, header.getContext())}
<div
className={styles.resize_handler}
onMouseDown={header.getResizeHandler()} //for desktop
onTouchStart={header.getResizeHandler()}
>
</div>
</Table.Th>
))}
</Table.Tr>
))}
</Table.Thead>
<Table.Tbody className={styles.tbody}>
{table.getRowModel().rows.map((row, rowIndex) => (
<Table.Tr key={row.id} className={styles.tr}>
{row.getVisibleCells().map(cell => {
const isEditing = editingCell.rowIndex === rowIndex && editingCell.columnId === cell.column.id;
return (
<Table.Td
key={cell.id}
onDoubleClick={() => setEditingCell({ rowIndex, columnId: cell.column.id })}
style={{ width: cell.column.getSize() }}
className={styles.td}
>
{isEditing ? (
<Input
type='text'
value={data[rowIndex][cell.column.id]}
onChange={(e) => handleEditCell(rowIndex, cell.column.id, e.target.value)}
onBlur={() => setEditingCell({ rowIndex: null, columnId: null })}
autoFocus
/>
) : (
flexRender(cell.column.columnDef.cell, cell.getContext())
)}
</Table.Td>
);
})}
</Table.Tr>
))}
</Table.Tbody>
</Table>
);
};
export default CustomTable;

View File

@ -11,26 +11,29 @@ import { Type } from 'ol/geom/Geometry'
import { click, never, noModifierKeys, platformModifierKeyOnly, primaryAction, shiftKeyOnly } from 'ol/events/condition'
import Feature from 'ol/Feature'
import { SatelliteMapsProvider } from '../../interfaces/map'
import { containsExtent, Extent, getCenter, getHeight, getWidth } from 'ol/extent'
import { containsExtent, Extent } from 'ol/extent'
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
import { mapCenter } from './MapConstants'
import ImageLayer from 'ol/layer/Image'
import VectorImageLayer from 'ol/layer/VectorImage'
import { LineString, MultiPoint, Point, Polygon, SimpleGeometry } from 'ol/geom'
import { fromExtent } from 'ol/geom/Polygon'
import { Circle, LineString, MultiPoint, Point, Polygon, SimpleGeometry } from 'ol/geom'
import { fromCircle, fromExtent } from 'ol/geom/Polygon'
import Collection from 'ol/Collection'
import { Coordinate } from 'ol/coordinate'
import { Stroke, Fill, Circle as CircleStyle, Style } from 'ol/style'
import { calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils'
import { Stroke, Fill, Circle as CircleStyle, Style, Text } from 'ol/style'
import { calculateCenter, calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils'
import MapBrowserEvent from 'ol/MapBrowserEvent'
import { fromLonLat, get } from 'ol/proj'
import { fromLonLat, get, transform } from 'ol/proj'
import { useCities } from '../../hooks/swrHooks'
import useSWR from 'swr'
import { fetcher } from '../../http/axiosInstance'
import { BASE_URL } from '../../constants'
import { Accordion, ActionIcon, Box, Flex, Select as MantineSelect, MantineStyleProp, rem, Slider, useMantineColorScheme } from '@mantine/core'
import { Accordion, ActionIcon, Box, Button, Flex, Select as MantineSelect, MantineStyleProp, rem, Slider, useMantineColorScheme } from '@mantine/core'
import { IconApi, IconArrowBackUp, IconArrowsMove, IconCircle, IconExclamationCircle, IconLine, IconPlus, IconPoint, IconPolygon, IconRuler, IconTable, IconUpload } from '@tabler/icons-react'
import { getGridCellPosition } from './mapUtils'
import { IFigure, ILine } from '../../interfaces/gis'
import { Height } from '@mui/icons-material'
const MapComponent = () => {
const { cities } = useCities(100, 1)
@ -97,6 +100,10 @@ const MapComponent = () => {
source: new VectorSource()
}))
const figuresLayer = useRef<VectorLayer>(new VectorLayer({
source: new VectorSource()
}))
const regionsLayer = useRef<VectorImageLayer>(new VectorImageLayer({
source: regionsLayerSource,
style: regionsLayerStyle
@ -208,49 +215,7 @@ const MapComponent = () => {
}),
});
function calculateCenter(geometry: SimpleGeometry) {
let center, coordinates, minRadius;
const type = geometry.getType();
if (type === 'Polygon') {
let x = 0;
let y = 0;
let i = 0;
coordinates = (geometry as Polygon).getCoordinates()[0].slice(1);
coordinates.forEach(function (coordinate) {
x += coordinate[0];
y += coordinate[1];
i++;
});
center = [x / i, y / i];
} else if (type === 'LineString') {
center = (geometry as LineString).getCoordinateAt(0.5);
coordinates = geometry.getCoordinates();
} else {
center = getCenter(geometry.getExtent());
}
let sqDistances;
if (coordinates) {
sqDistances = coordinates.map(function (coordinate: Coordinate) {
const dx = coordinate[0] - center[0];
const dy = coordinate[1] - center[1];
return dx * dx + dy * dy;
});
minRadius = Math.sqrt(Math.max.apply(Math, sqDistances)) / 3;
} else {
minRadius =
Math.max(
getWidth(geometry.getExtent()),
getHeight(geometry.getExtent()),
) / 3;
}
return {
center: center,
coordinates: coordinates,
minRadius: minRadius,
sqDistances: sqDistances,
};
}
// tile processing
const handleImageDrop = useCallback((event: any) => {
event.preventDefault();
event.stopPropagation();
@ -398,10 +363,10 @@ const MapComponent = () => {
const tileWidth = mapWidth / (Math.sqrt(Math.pow(4, zoomLevel)))
const tileHeight = mapHeight / (Math.sqrt(Math.pow(4, zoomLevel)))
let minPosX = minX - (tilesH / 2)
let maxPosX = maxX - (tilesH / 2) + 1
let minPosY = -(minY - (tilesH / 2))
let maxPosY = -(maxY - (tilesH / 2) + 1)
const minPosX = minX - (tilesH / 2)
const maxPosX = maxX - (tilesH / 2) + 1
const minPosY = -(minY - (tilesH / 2))
const maxPosY = -(maxY - (tilesH / 2) + 1)
console.log(`tileWidth: ${tileWidth} minPosX: ${minPosX} maxPosX: ${maxPosX} minPosY: ${minPosY} maxPosY: ${maxPosY}`)
const newMinX = tileWidth * minPosX
@ -551,36 +516,6 @@ const MapComponent = () => {
})
}
function getTilesPerSide(zoom: number) {
return Math.pow(2, zoom)
}
function normalize(value: number, min: number, max: number) {
return (value - min) / (max - min)
}
function getTileIndex(normalized: number, tilesPerSide: number) {
return Math.floor(normalized * tilesPerSide)
}
function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number) {
const tilesPerSide = getTilesPerSide(zoom);
const minX = extent[0]
const minY = extent[1]
const maxX = extent[2]
const maxY = extent[3]
// Normalize the coordinates
const xNormalized = normalize(x, minX, maxX);
const yNormalized = normalize(y, minY, maxY);
// Get tile indices
const tileX = getTileIndex(xNormalized, tilesPerSide);
const tileY = getTileIndex(1 - yNormalized, tilesPerSide);
return { tileX, tileY };
}
useEffect(() => {
drawingLayer.current = new VectorLayer({
source: drawingLayerSource.current,
@ -641,11 +576,11 @@ const MapComponent = () => {
map.current = new Map({
controls: [],
layers: [baseLayer.current, satLayer.current, regionsLayer.current, citiesLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current, nodeLayer.current],
layers: [baseLayer.current, satLayer.current, regionsLayer.current, citiesLayer.current, figuresLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current, nodeLayer.current],
target: mapElement.current as HTMLDivElement,
view: new View({
center: mapCenter,//center: fromLonLat([130.401113, 67.797368]),
zoom: 16,
center: transform([129.7659541, 62.009504], 'EPSG:4326', 'EPSG:3857'),//center: fromLonLat([130.401113, 67.797368]),
zoom: 17,
maxZoom: 21,
//extent: mapExtent,
}),
@ -729,7 +664,7 @@ const MapComponent = () => {
}
};
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(0)
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(1)
const [statusText, setStatusText] = useState('')
@ -797,7 +732,7 @@ const MapComponent = () => {
if (Array.isArray(nodes)) {
nodes.map(node => {
if (node.shape_type === 'LINE') {
let coordinates: Coordinate[] = []
const coordinates: Coordinate[] = []
if (Array.isArray(node.shape)) {
node.shape.map((point: any) => {
const coordinate = [point.x as number, point.y as number] as Coordinate
@ -811,6 +746,102 @@ const MapComponent = () => {
}
}, [nodes])
function styleFunction(feature: Feature) {
return [
new Style({
fill: new Fill({
color: 'rgba(255,255,255,0.4)'
}),
stroke: new Stroke({
color: '#3399CC',
width: 1.25
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: feature.get('object_id')
})
})
];
}
function fourthStyleFunction(feature: Feature) {
return [
new Style({
fill: new Fill({
color: 'rgba(255,255,255,0.4)'
}),
stroke: new Stroke({
color: '#3399CC',
width: 1.25
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: `${feature.get('object_id')}\n ${feature.get('angle')}`
})
})
];
}
function thirdStyleFunction(feature: Feature) {
return [
new Style({
fill: new Fill({
color: 'rgba(255,255,255,0.4)'
}),
stroke: new Stroke({
color: '#33ccb3',
width: 1.25
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: feature.get('object_id')
})
})
];
}
function firstStyleFunction(feature: Feature) {
return [
new Style({
fill: new Fill({
color: 'rgba(255,255,255,0.4)'
}),
stroke: new Stroke({
color: 'red',
width: 1.25
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({ color: '#000' }),
stroke: new Stroke({
color: '#fff', width: 2
}),
// get the text from the feature - `this` is ol.Feature
// and show only under certain resolution
text: feature.get('object_id')
})
})
];
}
return (
<Box w={'100%'} h={'calc(100% - 64px)'} mah={'100%'} flex={'1'} pos={'relative'}>
<ActionIcon.Group orientation='vertical' pos='absolute' top='8px' right='8px' style={{ zIndex: 1, backdropFilter: 'blur(8px)', backgroundColor: colorScheme === 'light' ? '#FFFFFFAA' : '#000000AA', borderRadius: '4px' }}>
@ -918,7 +949,160 @@ const MapComponent = () => {
<Accordion variant='filled' style={{ backgroundColor: 'transparent' }} defaultValue='Объекты'>
<Accordion.Item key={'s'} value={'Объекты'}>
<Accordion.Control icon={<IconTable />}>{'Объекты'}</Accordion.Control>
<Accordion.Panel>{'ASd'}</Accordion.Panel>
<Accordion.Panel>
<Button
onClick={async () => {
//open()
const city_id = 145
const year = 2023
const figuresLimit = 10000
const linesLimit = 10000
figuresLayer.current.getSource()?.clear()
try {
// const response = await fetch(`${import.meta.env.VITE_API_EMS_URL}/gis/images/all?city_id=${city_id}`, {
// method: 'GET',
// headers: {
// 'Content-Type': 'application/json',
// },
// })
//const responseData = await response.json()
const scaling = {
w: 10000, // responseData[0].width
h: 10000 // responseData[0].width
}
const figuresResponse = await fetch(`${import.meta.env.VITE_API_EMS_URL}/gis/figures/all?city_id=${city_id}&year=${year}&offset=0&limit=${figuresLimit}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
const linesResponse = await fetch(`${import.meta.env.VITE_API_EMS_URL}/gis/lines/all?city_id=${city_id}&year=${year}&offset=0&limit=${linesLimit}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
})
await linesResponse.json().then(linesData => {
linesData.map((line: ILine) => {
const x1 = line.x1 * scaling.w
const y1 = line.y1 * scaling.h
const x2 = line.x2 * scaling.w
const y2 = line.y2 * scaling.h
const center = [mapCenter[0], mapCenter[1]]
const testCoords = [
[center[0] + x1, center[1] - y1],
[center[0] + x2, center[1] - y2],
]
const feature = new Feature(new LineString(testCoords))
feature.setStyle(styleFunction(feature))
feature.set('object_id', line.object_id)
figuresLayer.current?.getSource()?.addFeature(feature)
})
})
await figuresResponse.json().then(figuresData => {
figuresData.map((figure: IFigure) => {
if (figure.figure_type_id == 1) {
const width = figure.width * scaling.w
const height = figure.height * scaling.h
const left = figure.left * scaling.w
const top = figure.top * scaling.h
const centerX = mapCenter[0] + left + (width / 2)
const centerY = mapCenter[1] - top - (height / 2)
const radius = width / 2;
const circleGeom = new Circle([centerX, centerY], radius)
const ellipseGeom = fromCircle(circleGeom, 64)
ellipseGeom.scale(1, height / width)
const feature = new Feature(ellipseGeom)
feature.setStyle(firstStyleFunction(feature))
feature.set('object_id', figure.object_id)
figuresLayer.current?.getSource()?.addFeature(feature)
}
if (figure.figure_type_id == 3) {
const x = figure.left * scaling.w
const y = figure.top * scaling.h
const center = [mapCenter[0] + x, mapCenter[1] - y]
const coords = figure.points?.split(' ').map(pair => {
const [x, y] = pair.split(';').map(Number)
return [
center[0] + (x * scaling.w),
center[1] - (y * scaling.h)
]
})
if (coords) {
const polygon = new Polygon([coords])
const feature = new Feature({
geometry: polygon
})
feature.set('object_id', figure.object_id)
feature.setStyle(thirdStyleFunction(feature))
figuresLayer.current?.getSource()?.addFeature(feature)
}
}
if (figure.figure_type_id == 4) {
const width = figure.width * scaling.w
const height = figure.height * scaling.h
const left = figure.left * scaling.w
const top = figure.top * scaling.h
const halfWidth = width / 2
const halfHeight = height / 2
const center = [mapCenter[0] + left + halfWidth, mapCenter[1] - top - halfHeight]
const testCoords = [
[center[0] - halfWidth, center[1] - halfHeight],
[center[0] - halfWidth, center[1] + halfHeight],
[center[0] + halfWidth, center[1] + halfHeight],
[center[0] + halfWidth, center[1] - halfHeight],
[center[0] - halfWidth, center[1] - halfHeight]
]
const geometry1 = new Polygon([testCoords])
const anchor1 = center
geometry1.rotate(-figure.angle * Math.PI / 180, anchor1)
const feature1 = new Feature(geometry1)
feature1.set('object_id', figure.object_id)
feature1.set('angle', figure.angle)
feature1.setStyle(fourthStyleFunction(feature1))
figuresLayer.current?.getSource()?.addFeature(feature1)
}
})
})
} catch (err) {
console.error('No data')
}
}}
>
Test
</Button>
</Accordion.Panel>
</Accordion.Item>
</Accordion>
</Flex>
@ -956,7 +1140,7 @@ const MapComponent = () => {
}}
>
</div>
</Box>
</Box >
);
};

View File

@ -1,5 +1,6 @@
import { Coordinate, distance, rotate } from "ol/coordinate";
import { Extent, getCenter } from "ol/extent";
import { Extent, getCenter, getHeight, getWidth } from "ol/extent";
import { LineString, Polygon, SimpleGeometry } from "ol/geom";
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
import proj4 from "proj4";
@ -119,9 +120,87 @@ function calculateExtent(bottomLeft: Coordinate, topLeft: Coordinate, topRight:
return extent;
}
function getTilesPerSide(zoom: number) {
return Math.pow(2, zoom)
}
function normalize(value: number, min: number, max: number) {
return (value - min) / (max - min)
}
function getTileIndex(normalized: number, tilesPerSide: number) {
return Math.floor(normalized * tilesPerSide)
}
function getGridCellPosition(x: number, y: number, extent: Extent, zoom: number) {
const tilesPerSide = getTilesPerSide(zoom);
const minX = extent[0]
const minY = extent[1]
const maxX = extent[2]
const maxY = extent[3]
// Normalize the coordinates
const xNormalized = normalize(x, minX, maxX);
const yNormalized = normalize(y, minY, maxY);
// Get tile indices
const tileX = getTileIndex(xNormalized, tilesPerSide);
const tileY = getTileIndex(1 - yNormalized, tilesPerSide);
return { tileX, tileY };
}
function calculateCenter(geometry: SimpleGeometry) {
let center, coordinates, minRadius;
const type = geometry.getType();
if (type === 'Polygon') {
let x = 0;
let y = 0;
let i = 0;
coordinates = (geometry as Polygon).getCoordinates()[0].slice(1);
coordinates.forEach(function (coordinate) {
x += coordinate[0];
y += coordinate[1];
i++;
});
center = [x / i, y / i];
} else if (type === 'LineString') {
center = (geometry as LineString).getCoordinateAt(0.5);
coordinates = geometry.getCoordinates();
} else {
center = getCenter(geometry.getExtent());
}
let sqDistances;
if (coordinates) {
sqDistances = coordinates.map(function (coordinate: Coordinate) {
const dx = coordinate[0] - center[0];
const dy = coordinate[1] - center[1];
return dx * dx + dy * dy;
});
minRadius = Math.sqrt(Math.max.apply(Math, sqDistances)) / 3;
} else {
minRadius =
Math.max(
getWidth(geometry.getExtent()),
getHeight(geometry.getExtent()),
) / 3;
}
return {
center: center,
coordinates: coordinates,
minRadius: minRadius,
sqDistances: sqDistances,
};
}
export {
rotateProjection,
calculateRotationAngle,
calculateExtent,
calculateCentroid
calculateCentroid,
getTilesPerSide,
normalize,
getTileIndex,
getGridCellPosition,
calculateCenter
}