Map caching in Redis

This commit is contained in:
cracklesparkle
2024-08-23 17:50:53 +09:00
parent 97b44a4db7
commit 579bbf7764
23 changed files with 688 additions and 143 deletions

View File

@ -3,34 +3,124 @@ import GeoJSON from 'ol/format/GeoJSON'
import 'ol/ol.css'
import Map from 'ol/Map'
import View from 'ol/View'
import { Draw, Modify, Snap } from 'ol/interaction'
import { OSM, Vector as VectorSource } from 'ol/source'
import { Draw, Modify, Select, Snap, Translate } from 'ol/interaction'
import { OSM, Source, Vector as VectorSource, XYZ } from 'ol/source'
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer'
import { transform, transformExtent } from 'ol/proj'
import { Divider, IconButton, Stack } from '@mui/material'
import { Adjust, Api, CircleOutlined, RectangleOutlined, Timeline, Undo, Warning } from '@mui/icons-material'
import { get, Projection, transform, transformExtent } from 'ol/proj'
import { Divider, IconButton, Slider, Stack, Select as MUISelect, MenuItem, Container, Box } from '@mui/material'
import { Adjust, Api, CircleOutlined, DoubleArrow, FeaturedVideoSharp, Handyman, OpenWith, RectangleOutlined, Timeline, Undo, Warning } from '@mui/icons-material'
import { Type } from 'ol/geom/Geometry'
import { altKeyOnly, click, doubleClick, noModifierKeys, platformModifierKey, pointerMove, shiftKeyOnly, singleClick } from 'ol/events/condition'
import Feature, { FeatureLike } from 'ol/Feature'
import Style from 'ol/style/Style'
import Fill from 'ol/style/Fill'
import Stroke from 'ol/style/Stroke'
import { FlatStyleLike } from 'ol/style/flat'
import { SatelliteMapsProvider } from '../../interfaces/map'
import Tile from 'ol/Tile'
import ImageTile from 'ol/ImageTile'
import { createXYZ, TileGrid } from 'ol/tilegrid'
import { TileCoord } from 'ol/tilecoord'
import { register } from 'ol/proj/proj4'
import proj4 from 'proj4'
const MapComponent = () => {
proj4.defs('EPSG:3395', '+proj=merc +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs')
register(proj4);
const yandexProjection = get('EPSG:3395')?.setExtent([-20037508.342789244, -20037508.342789244, 20037508.342789244, 20037508.342789244]) || 'EPSG:3395'
const mapElement = useRef<HTMLDivElement | null>(null)
const [currentTool, setCurrentTool] = useState<Type>('Point')
const [currentTool, setCurrentTool] = useState<Type | null>(null)
const map = useRef<Map | null>(null)
const source = useRef<VectorSource>(new VectorSource())
const [satMapsProvider, setSatMapsProvider] = useState<SatelliteMapsProvider>('yandex')
const gMapsSatSource = useRef<XYZ>(new XYZ({
//url: `https://khms2.google.com/kh/v=984?x={x}&y={y}&z={z}`,
url: `http://localhost:5000/tile/google/{z}/{x}/{y}`,
attributions: 'Map data © Google'
}))
const yMapsSatSource = useRef<XYZ>(new XYZ({
//url: `https://sat0{1-4}.maps.yandex.net/tiles?l=sat&x={x}&y={y}&z={z}&scale=1&lang=ru_RU&client_id=yandex-web-maps`,
url: `https://core-sat.maps.yandex.net/tiles?l=sat&x={x}&y={y}&z={z}&scale=1&lang=ru_RU`,
attributions: 'Map data © Yandex',
projection: yandexProjection,
}))
const satLayer = useRef<TileLayer>(new TileLayer({
source: gMapsSatSource.current,
}))
const draw = useRef<Draw | null>(null)
const snap = useRef<Snap | null>(null)
const selectFeature = useRef<Select | null>(null)
const drawingLayer = useRef<VectorLayer | null>(null)
const drawingLayerSource = useRef<VectorSource>(new VectorSource())
const regionsLayer = useRef<VectorLayer>(new VectorLayer({
source: new VectorSource({
url: 'sakha_republic.geojson',
format: new GeoJSON(),
}),
style: new Style({
stroke: new Stroke({
color: 'blue',
width: 1,
}),
fill: new Fill({
color: 'rgba(0, 0, 255, 0.1)',
}),
}),
}))
const selectStyle = new Style({
fill: new Fill({
color: 'rgba(0, 0, 255, 0.3)',
}),
stroke: new Stroke({
color: 'rgba(255, 255, 255, 0.7)',
width: 2,
}),
})
const selectedRegion = useRef<Feature | null>(null)
const geoLayer = useRef<VectorLayer | null>(null)
const geoLayerSource = useRef<VectorSource>(new VectorSource({
url: 'https://openlayers.org/data/vector/ecoregions.json',
format: new GeoJSON(),
}))
const baseLayer = useRef<TileLayer | null>(null)
const geoLayerStyle: FlatStyleLike = {
'fill-color': ['string', ['get', 'COLOR'], '#eee'],
}
const drawingLayerStyle: FlatStyleLike = {
'fill-color': 'rgba(255, 255, 255, 0.2)',
'stroke-color': '#ffcc33',
'stroke-width': 2,
'circle-radius': 7,
'circle-fill-color': '#ffcc33',
}
const addInteractions = () => {
draw.current = new Draw({
source: source.current,
type: currentTool,
})
map?.current?.addInteraction(draw.current)
snap.current = new Snap({ source: source.current })
map?.current?.addInteraction(snap.current)
if (currentTool) {
draw.current = new Draw({
source: drawingLayerSource.current,
type: currentTool,
condition: noModifierKeys
})
map?.current?.addInteraction(draw.current)
snap.current = new Snap({ source: drawingLayerSource.current })
map?.current?.addInteraction(snap.current)
}
}
// Function to save features to localStorage
@ -41,6 +131,8 @@ const MapComponent = () => {
const featuresJSON = geoJSON.writeFeatures(features)
localStorage.setItem('savedFeatures', featuresJSON)
}
console.log(drawingLayer.current?.getSource()?.getFeatures())
}
// Function to load features from localStorage
@ -51,40 +143,37 @@ const MapComponent = () => {
const features = geoJSON.readFeatures(savedFeatures, {
featureProjection: 'EPSG:4326', // Ensure the projection is correct
})
source.current?.addFeatures(features) // Add features to the vector source
drawingLayerSource.current?.addFeatures(features) // Add features to the vector source
//drawingLayer.current?.getSource()?.changed()
}
}
const handleToolSelect = (tool: Type) => {
if (currentTool == tool) {
setCurrentTool(null)
} else {
setCurrentTool(tool)
}
}
useEffect(() => {
const geoLayer = new VectorLayer({
geoLayer.current = new VectorLayer({
background: '#1a2b39',
source: new VectorSource({
url: 'https://openlayers.org/data/vector/ecoregions.json',
format: new GeoJSON(),
}),
style: {
'fill-color': ['string', ['get', 'COLOR'], '#eee'],
},
source: geoLayerSource.current,
style: geoLayerStyle,
})
const raster = new TileLayer({
baseLayer.current = new TileLayer({
source: new OSM(),
})
drawingLayer.current = new VectorLayer({
source: source.current,
style: {
'fill-color': 'rgba(255, 255, 255, 0.2)',
'stroke-color': '#ffcc33',
'stroke-width': 2,
'circle-radius': 7,
'circle-fill-color': '#ffcc33',
},
source: drawingLayerSource.current,
style: drawingLayerStyle,
})
// Center coordinates of Yakutia in EPSG:3857
const center = transform([129.7694, 66.9419], 'EPSG:4326', 'EPSG:3857')
const center = transform([129.7578941, 62.030804], 'EPSG:4326', 'EPSG:3857')
// Extent for Yakutia in EPSG:4326
const extent4326 = [105.0, 55.0, 170.0, 75.0] // Approximate bounding box
@ -92,22 +181,64 @@ const MapComponent = () => {
const extent = transformExtent(extent4326, 'EPSG:4326', 'EPSG:3857')
map.current = new Map({
layers: [geoLayer, raster, drawingLayer.current],
layers: [baseLayer.current, satLayer.current, regionsLayer.current, drawingLayer.current],
target: mapElement.current as HTMLDivElement,
view: new View({
center,
zoom: 4,
extent,
zoom: 2,
extent: [
11388546.533293726,
7061866.113051185,
18924313.434856508,
13932243.11199202
],
}),
})
const modify = new Modify({ source: source.current })
const modify = new Modify({ source: drawingLayerSource.current })
map.current.addInteraction(modify)
addInteractions()
selectFeature.current = new Select({
condition: function (mapBrowserEvent) {
return click(mapBrowserEvent) && shiftKeyOnly(mapBrowserEvent);
},
})
map.current.addInteraction(selectFeature.current)
selectFeature.current.on('select', (e) => {
const selectedFeatures = e.selected
console.log(selectedFeatures)
if (selectedFeatures.length > 0) {
selectedFeatures.forEach((feature) => {
drawingLayerSource.current?.removeFeature(feature)
})
}
})
loadFeatures()
map.current.on('pointermove', function (e) {
if (selectedRegion.current !== null) {
selectedRegion.current.setStyle(undefined)
selectedRegion.current = null
}
if (map.current && selectStyle !== null) {
map.current.forEachFeatureAtPixel(e.pixel, function (f) {
selectedRegion.current = f as Feature
selectedRegion.current.setStyle(selectStyle)
if (f.get('district')) {
setStatusText(f.get('district'))
}
return true
})
}
})
return () => {
map?.current?.setTarget(undefined)
}
@ -118,12 +249,61 @@ const MapComponent = () => {
if (draw.current) map?.current?.removeInteraction(draw.current)
if (snap.current) map?.current?.removeInteraction(snap.current)
addInteractions()
} else {
if (draw.current) map?.current?.removeInteraction(draw.current)
if (snap.current) map?.current?.removeInteraction(snap.current)
}
}, [currentTool])
const [satelliteOpacity, setSatelliteOpacity] = useState<number>(0)
const [statusText, setStatusText] = useState('')
const selected = useRef<FeatureLike | null>(null)
// Visibility setting
useEffect(() => {
satLayer.current?.setOpacity(satelliteOpacity)
if (satelliteOpacity == 0) {
baseLayer.current?.setVisible(true)
satLayer.current?.setVisible(false)
} if (satelliteOpacity == 1) {
baseLayer.current?.setVisible(false)
satLayer.current?.setVisible(true)
} else if (satelliteOpacity > 0 && satelliteOpacity < 1) {
baseLayer.current?.setVisible(true)
satLayer.current?.setVisible(true)
}
}, [satelliteOpacity])
// Satellite tiles setting
useEffect(() => {
satLayer.current?.setSource(satMapsProvider == 'google' ? gMapsSatSource.current : satMapsProvider == 'yandex' ? yMapsSatSource.current : gMapsSatSource.current)
satLayer.current?.getSource()?.refresh()
}, [satMapsProvider])
return (
<div>
<Stack flex={1} flexDirection='column'>
<Stack my={1} spacing={1} direction='row' divider={<Divider orientation='vertical' flexItem />}>
<Stack flex={1} alignItems='center' justifyContent='center'>
<Slider aria-label="Opacity" min={0} max={1} step={0.001} defaultValue={satelliteOpacity} value={satelliteOpacity} onChange={(_, value) => setSatelliteOpacity(Array.isArray(value) ? value[0] : value)} />
</Stack>
<MUISelect
variant='standard'
labelId="demo-simple-select-label"
id="demo-simple-select"
value={satMapsProvider}
label="Satellite Provider"
onChange={(e) => setSatMapsProvider(e.target.value as SatelliteMapsProvider)}
>
<MenuItem value={'google'}>Google</MenuItem>
<MenuItem value={'yandex'}>Яндекс</MenuItem>
</MUISelect>
<IconButton onClick={() => {
fetch(`${import.meta.env.VITE_API_EMS_URL}/hello`, { method: 'GET' }).then(res => console.log(res))
}}>
@ -145,30 +325,43 @@ const MapComponent = () => {
<IconButton
sx={{ backgroundColor: currentTool === 'Point' ? 'Highlight' : 'transparent' }}
onClick={() => setCurrentTool('Point')}>
onClick={() => handleToolSelect('Point')}>
<Adjust />
</IconButton>
<IconButton
sx={{ backgroundColor: currentTool === 'LineString' ? 'Highlight' : 'transparent' }}
onClick={() => setCurrentTool('LineString')}>
onClick={() => handleToolSelect('LineString')}>
<Timeline />
</IconButton>
<IconButton
sx={{ backgroundColor: currentTool === 'Polygon' ? 'Highlight' : 'transparent' }}
onClick={() => setCurrentTool('Polygon')}>
onClick={() => handleToolSelect('Polygon')}>
<RectangleOutlined />
</IconButton>
<IconButton
sx={{ backgroundColor: currentTool === 'Circle' ? 'Highlight' : 'transparent' }}
onClick={() => setCurrentTool('Circle')}>
onClick={() => handleToolSelect('Circle')}>
<CircleOutlined />
</IconButton>
<IconButton
onClick={() => map?.current?.addInteraction(new Translate())}
>
<OpenWith />
</IconButton>
</Stack>
<div ref={mapElement} style={{ width: '100%', height: '400px' }}></div>
</div>
<Box>
<div id="map-container" ref={mapElement} style={{ width: '100%', height: '500px', maxHeight: '100%', position: 'relative', flexGrow: 1 }}></div>
</Box>
<Stack>
{statusText}
</Stack>
</Stack>
);
};