Map experiments

This commit is contained in:
cracklesparkle
2024-09-06 17:56:42 +09:00
parent 3994989994
commit ddacbcd837
9 changed files with 1098 additions and 134 deletions

View File

@ -15,11 +15,12 @@ import Documents from "./pages/Documents"
import Reports from "./pages/Reports"
import Boilers from "./pages/Boilers"
import Servers from "./pages/Servers"
import { Api, Assignment, Cloud, Factory, Home, Login, Map, MonitorHeart, Password, People, Settings as SettingsIcon, Shield, Storage } from "@mui/icons-material"
import { Api, Assignment, Cloud, Factory, Home, Login, Map, MonitorHeart, Password, People, Settings as SettingsIcon, Shield, Storage, Warning } from "@mui/icons-material"
import Settings from "./pages/Settings"
import PasswordReset from "./pages/auth/PasswordReset"
import MapTest from "./pages/MapTest"
import MonitorPage from "./pages/MonitorPage"
import ChunkedUpload from "./components/map/ChunkedUpload"
// Определение страниц с путями и компонентом для рендера
export const pages = [
@ -127,6 +128,14 @@ export const pages = [
drawer: true,
dashboard: true
},
{
label: "Chunk test",
path: "/chunk-test",
icon: <Warning />,
component: <ChunkedUpload />,
drawer: true,
dashboard: true
},
{
label: "Монитор",
path: "/monitor",

View File

@ -0,0 +1,58 @@
import React, { useState } from 'react';
import axios from 'axios';
const ChunkedUpload = () => {
const [file, setFile] = useState<File | null>(null);
const [uploadProgress, setUploadProgress] = useState<number>(0);
// Handle file selection
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
setFile(event.target.files[0]);
}
};
// Upload the file in chunks
const uploadFile = async () => {
if (!file) return;
const chunkSize = 1024 * 1024; // 1MB per chunk
const totalChunks = Math.ceil(file.size / chunkSize);
const fileId = `${file.name}-${Date.now()}`; // Unique file identifier
let uploadedChunks = 0;
for (let start = 0; start < file.size; start += chunkSize) {
const chunk = file.slice(start, start + chunkSize);
const chunkNumber = Math.ceil(start / chunkSize) + 1;
try {
await axios.post(`${import.meta.env.VITE_API_EMS_URL}/upload`, chunk, {
headers: {
'Content-Type': 'application/octet-stream',
'X-Chunk-Number': chunkNumber.toString(),
'X-Total-Chunks': totalChunks.toString(),
'X-File-Id': fileId,
},
});
uploadedChunks++;
setUploadProgress((uploadedChunks / totalChunks) * 100);
} catch (error) {
console.error('Chunk upload failed', error);
// Implement retry logic if needed
break;
}
}
};
return (
<div>
<input type="file" onChange={handleFileChange} />
<button onClick={uploadFile} disabled={!file}>
Upload File
</button>
<div>Upload Progress: {uploadProgress.toFixed(2)}%</div>
</div>
);
};
export default ChunkedUpload;

View File

@ -4,29 +4,35 @@ import 'ol/ol.css'
import Map from 'ol/Map'
import View from 'ol/View'
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
import { ImageStatic, OSM, Vector as VectorSource, XYZ } from 'ol/source'
import { ImageStatic, OSM, TileDebug, Vector as VectorSource, XYZ } from 'ol/source'
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
import { Divider, IconButton, Slider, Stack, Select as MUISelect, MenuItem, Box } from '@mui/material'
import { Divider, IconButton, Slider, Stack, Select as MUISelect, MenuItem, Box, Typography } from '@mui/material'
import { Add, Adjust, Api, CircleOutlined, OpenWith, RectangleOutlined, Straighten, Timeline, Undo, Warning } from '@mui/icons-material'
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 { boundingExtent, containsExtent, Extent, getBottomLeft, getBottomRight, getCenter, getHeight, getTopLeft, getTopRight, getWidth } from 'ol/extent'
import { containsExtent, Extent, getCenter, getHeight, getWidth } from 'ol/extent'
import { drawingLayerStyle, regionsLayerStyle, selectStyle } from './MapStyles'
import { googleMapsSatelliteSource, regionsLayerSource, yandexMapsSatelliteSource } from './MapSources'
import { mapCenter } from './MapConstants'
import { mapCenter, mapExtent } 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 Collection from 'ol/Collection'
import { Coordinate, distance, rotate } from 'ol/coordinate'
import { Coordinate } from 'ol/coordinate'
import { Stroke, Fill, Circle as CircleStyle, Style } from 'ol/style'
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, transform } from 'ol/proj'
import proj4 from 'proj4'
import { calculateExtent, calculateRotationAngle, rotateProjection } from './mapUtils'
import MapBrowserEvent from 'ol/MapBrowserEvent'
import { get } from 'ol/proj'
const MapComponent = () => {
const [currentCoordinate, setCurrentCoordinate] = useState<Coordinate | null>(null)
const [currentZ, setCurrentZ] = useState<number | undefined>(undefined)
const [currentX, setCurrentX] = useState<number | undefined>(undefined)
const [currentY, setCurrentY] = useState<number | undefined>(undefined)
const mapElement = useRef<HTMLDivElement | null>(null)
const [currentTool, setCurrentTool] = useState<Type | null>(null)
@ -191,88 +197,6 @@ const MapComponent = () => {
};
}
function rotateProjection(projection, angle, extent) {
function rotateCoordinate(coordinate, angle, anchor) {
var coord = rotate(
[coordinate[0] - anchor[0], coordinate[1] - anchor[1]],
angle
);
return [coord[0] + anchor[0], coord[1] + anchor[1]];
}
function rotateTransform(coordinate: Coordinate) {
return rotateCoordinate(coordinate, angle, getCenter(extent));
}
function normalTransform(coordinate: Coordinate) {
return rotateCoordinate(coordinate, -angle, getCenter(extent));
}
var normalProjection = get(projection);
var rotatedProjection = new Projection({
code:
normalProjection.getCode() +
":" +
angle.toString() +
":" +
extent.toString(),
units: normalProjection.getUnits(),
extent: extent
});
addProjection(rotatedProjection);
addCoordinateTransforms(
"EPSG:4326",
rotatedProjection,
function (coordinate) {
return rotateTransform(transform(coordinate, "EPSG:4326", projection));
},
function (coordinate) {
return transform(normalTransform(coordinate), projection, "EPSG:4326");
}
);
addCoordinateTransforms(
"EPSG:3857",
rotatedProjection,
function (coordinate) {
return rotateTransform(transform(coordinate, "EPSG:3857", projection));
},
function (coordinate) {
return transform(normalTransform(coordinate), projection, "EPSG:3857");
}
);
// also set up transforms with any projections defined using proj4
if (typeof proj4 !== "undefined") {
var projCodes = Object.keys(proj4.defs);
projCodes.forEach(function (code) {
var proj4Projection = get(code);
if (!getTransform(proj4Projection, rotatedProjection)) {
addCoordinateTransforms(
proj4Projection,
rotatedProjection,
function (coordinate) {
return rotateTransform(
transform(coordinate, proj4Projection, projection)
);
},
function (coordinate) {
return transform(
normalTransform(coordinate),
projection,
proj4Projection
);
}
);
}
});
}
return rotatedProjection;
}
const handleImageDrop = useCallback((event: any) => {
event.preventDefault();
event.stopPropagation();
@ -383,41 +307,6 @@ const MapComponent = () => {
}
});
const calculateCentroid = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => {
const x = (bottomLeft[0] + topLeft[0] + topRight[0] + bottomRight[0]) / 4;
const y = (bottomLeft[1] + topLeft[1] + topRight[1] + bottomRight[1]) / 4;
return [x, y];
}
const calculateRotationAngle = (bottomLeft: Coordinate, bottomRight: Coordinate) => {
// Calculate the difference in x and y coordinates between bottom right and bottom left
const deltaX = bottomRight[0] - bottomLeft[0];
const deltaY = bottomRight[1] - bottomLeft[1];
// Calculate the angle using atan2
const angle = Math.atan2(deltaY, deltaX);
return angle;
}
const calculateExtent = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => {
const width = distance(bottomLeft, bottomRight);
const height = distance(bottomLeft, topLeft);
// Calculate the centroid of the polygon
const [centerX, centerY] = calculateCentroid(bottomLeft, topLeft, topRight, bottomRight);
// Define the extent based on the center and dimensions
const extent = [
centerX - width / 2, // minX
centerY - height / 2, // minY
centerX + width / 2, // maxX
centerY + height / 2 // maxY
];
return extent;
}
// Function to update the image layer with a new source when extent changes
const updateImageSource = () => {
const newExtent = polygonFeature.getGeometry()?.getExtent();
@ -430,10 +319,34 @@ const MapComponent = () => {
if (newExtent && bottomLeft && bottomRight && topRight && topLeft) {
const originalExtent = calculateExtent(bottomLeft, topLeft, topRight, bottomRight)
const worldExtent = get('EPSG:3857')?.getExtent() as Extent
const zoomLevel = Number(map.current?.getView().getZoom()?.toFixed(0))
const { tileX: blX, tileY: blY } = getGridCellPosition(bottomLeft[0], bottomLeft[1], worldExtent, zoomLevel)
const { tileX: tlX, tileY: tlY } = getGridCellPosition(topLeft[0], topLeft[1], worldExtent, zoomLevel)
const { tileX: trX, tileY: trY } = getGridCellPosition(topRight[0], topRight[1], worldExtent, zoomLevel)
const { tileX: brX, tileY: brY } = getGridCellPosition(bottomRight[0], topRight[1], worldExtent, zoomLevel)
const minX = Math.min(blX, tlX, trX, brX)
const maxX = Math.max(blX, tlX, trX, brX)
const minY = Math.min(blY, tlY, trY, brY)
const maxY = Math.max(blY, tlY, trY, brY)
const mapWidth = Math.abs(worldExtent[0] - worldExtent[2])
const mapHeight = Math.abs(worldExtent[1] - worldExtent[3])
const tileWidth = mapWidth / (zoomLevel * 4)
const tileHeight = mapHeight / (zoomLevel * 4)
const newMinX = worldExtent[0] + (tileWidth * minX)
const newMaxX = worldExtent[0] + (tileWidth * (maxX + 1))
const newMinY = worldExtent[1] + (tileHeight * (maxY + 1))
const newMaxY = worldExtent[1] + (tileHeight * minY)
console.log('Tile slippy bounds: ', minX, maxX, minY, maxY)
console.log('Tile bounds: ', newMinX, newMaxX, newMinY, newMaxY)
const newImageSource = new ImageStatic({
url: imageUrl,
imageExtent: originalExtent,
projection: rotateProjection('EPSG:3857', -calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
projection: rotateProjection('EPSG:3857', calculateRotationAngle(bottomLeft, bottomRight), originalExtent)
});
imageLayer.current.setSource(newImageSource);
}
@ -535,6 +448,36 @@ 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,
@ -589,7 +532,9 @@ const MapComponent = () => {
})
map.current = new Map({
layers: [baseLayer.current, satLayer.current, regionsLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current],
layers: [baseLayer.current, new TileLayer({
source: new TileDebug(),
}), satLayer.current, regionsLayer.current, drawingLayer.current, imageLayer.current, overlayLayer.current],
target: mapElement.current as HTMLDivElement,
view: new View({
center: mapCenter,
@ -599,6 +544,16 @@ const MapComponent = () => {
}),
})
map.current.on('pointermove', function (e: MapBrowserEvent<any>) {
setCurrentCoordinate(e.coordinate)
const currentExtent = get('EPSG:3857')?.getExtent() as Extent
const { tileX, tileY } = getGridCellPosition(e.coordinate[0], e.coordinate[1], currentExtent, Number(map.current?.getView().getZoom()?.toFixed(0)))
setCurrentZ(Number(map.current?.getView().getZoom()?.toFixed(0)))
setCurrentX(tileX)
setCurrentY(tileY)
})
const modify = new Modify({ source: drawingLayerSource.current })
map.current.addInteraction(modify)
@ -678,6 +633,16 @@ const MapComponent = () => {
<IconButton title='Добавить подложку'>
<Add />
</IconButton>
<Typography>
{currentCoordinate?.[0]}-{currentCoordinate?.[1]}
</Typography>
<Typography>
Z={currentZ}
X={currentX}
Y={currentY}
</Typography>
</Stack>
<Stack my={1} spacing={1} direction='row' divider={<Divider orientation='vertical' flexItem />}>

View File

@ -0,0 +1,127 @@
import { Coordinate, distance, rotate } from "ol/coordinate";
import { Extent, getCenter } from "ol/extent";
import { addCoordinateTransforms, addProjection, get, getTransform, Projection, ProjectionLike, transform } from "ol/proj";
import proj4 from "proj4";
function rotateProjection(projection: ProjectionLike, angle: number, extent: Extent) {
function rotateCoordinate(coordinate: Coordinate, angle: number, anchor: Coordinate) {
var coord = rotate(
[coordinate[0] - anchor[0], coordinate[1] - anchor[1]],
angle
);
return [coord[0] + anchor[0], coord[1] + anchor[1]];
}
function rotateTransform(coordinate: Coordinate) {
return rotateCoordinate(coordinate, angle, getCenter(extent));
}
function normalTransform(coordinate: Coordinate) {
return rotateCoordinate(coordinate, -angle, getCenter(extent));
}
var normalProjection = get(projection);
if (normalProjection) {
var rotatedProjection = new Projection({
code: normalProjection.getCode() + ":" + angle.toString() + ":" + extent.toString(),
units: normalProjection.getUnits(),
extent: extent
});
addProjection(rotatedProjection);
addCoordinateTransforms(
"EPSG:4326",
rotatedProjection,
function (coordinate) {
return rotateTransform(transform(coordinate, "EPSG:4326", projection));
},
function (coordinate) {
return transform(normalTransform(coordinate), projection, "EPSG:4326");
}
);
addCoordinateTransforms(
"EPSG:3857",
rotatedProjection,
function (coordinate) {
return rotateTransform(transform(coordinate, "EPSG:3857", projection));
},
function (coordinate) {
return transform(normalTransform(coordinate), projection, "EPSG:3857");
}
);
// also set up transforms with any projections defined using proj4
if (typeof proj4 !== "undefined") {
var projCodes = Object.keys(proj4.defs);
projCodes.forEach(function (code) {
var proj4Projection = get(code) as Projection;
if (proj4Projection) {
if (!getTransform(proj4Projection, rotatedProjection)) {
addCoordinateTransforms(
proj4Projection,
rotatedProjection,
function (coordinate) {
return rotateTransform(
transform(coordinate, proj4Projection, projection)
);
},
function (coordinate) {
return transform(
normalTransform(coordinate),
projection,
proj4Projection
);
}
);
}
}
});
}
return rotatedProjection;
}
}
const calculateCentroid = (bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) => {
const x = (bottomLeft[0] + topLeft[0] + topRight[0] + bottomRight[0]) / 4;
const y = (bottomLeft[1] + topLeft[1] + topRight[1] + bottomRight[1]) / 4;
return [x, y];
}
function calculateRotationAngle(bottomLeft: Coordinate, bottomRight: Coordinate) {
// Calculate the difference in x and y coordinates between bottom right and bottom left
const deltaX = bottomRight[0] - bottomLeft[0];
const deltaY = bottomRight[1] - bottomLeft[1];
// Calculate the angle using atan2
const angle = -Math.atan2(deltaY, deltaX);
return angle;
}
function calculateExtent(bottomLeft: Coordinate, topLeft: Coordinate, topRight: Coordinate, bottomRight: Coordinate) {
const width = distance(bottomLeft, bottomRight);
const height = distance(bottomLeft, topLeft);
// Calculate the centroid of the polygon
const [centerX, centerY] = calculateCentroid(bottomLeft, topLeft, topRight, bottomRight);
// Define the extent based on the center and dimensions
const extent = [
centerX - width / 2, // minX
centerY - height / 2, // minY
centerX + width / 2, // maxX
centerY + height / 2 // maxY
];
return extent;
}
export {
rotateProjection,
calculateRotationAngle,
calculateExtent,
calculateCentroid
}