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

@ -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 />}>