Fixed Viewport
This commit is contained in:
parent
fc0c6b1b1f
commit
1c4cf0b8d4
2 changed files with 552 additions and 152 deletions
27
src/components/UI/Map/OpenLayerMap.css
Normal file
27
src/components/UI/Map/OpenLayerMap.css
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
#root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For your map container specifically */
|
||||||
|
.map-container {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure OL map gets proper dimensions */
|
||||||
|
.ol-viewport {
|
||||||
|
width: 100% !important;
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
@ -7,33 +7,42 @@ import { fromLonLat } from 'ol/proj';
|
||||||
import { Coordinate } from 'ol/coordinate';
|
import { Coordinate } from 'ol/coordinate';
|
||||||
import VectorLayer from 'ol/layer/Vector';
|
import VectorLayer from 'ol/layer/Vector';
|
||||||
import { layerConfigs } from '../../Methods/Layers';
|
import { layerConfigs } from '../../Methods/Layers';
|
||||||
import { loadAndRenderGeoJsonLayer } from '../../Methods/GeoJson_Load'
|
import { loadAndRenderGeoJsonLayer } from '../../Methods/GeoJson_Load';
|
||||||
import { addMarker } from '../../Methods/Marker';
|
import { addMarker } from '../../Methods/Marker';
|
||||||
|
|
||||||
const OpenLayersMap = () => {
|
const OpenLayersMap = () => {
|
||||||
const [term, setTerm] = useState<string>('');
|
const [term, setTerm] = useState<string>('');
|
||||||
const [address, setAddress] = useState();
|
const [address, setAddress] = useState<any>();
|
||||||
const [layerVisibility, setLayerVisibility] = useState<Record<string, boolean>>({});
|
const [layerVisibility, setLayerVisibility] = useState<Record<string, boolean>>({});
|
||||||
const [searchCompleted, setSearchCompleted] = useState(false);
|
const [searchCompleted, setSearchCompleted] = useState(false);
|
||||||
|
const [sidebarOpen, setSidebarOpen] = useState(true);
|
||||||
|
const [isMobile, setIsMobile] = useState(window.innerWidth < 768);
|
||||||
|
|
||||||
|
// Check for mobile view
|
||||||
|
useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setIsMobile(window.innerWidth < 768);
|
||||||
|
if (window.innerWidth >= 768) {
|
||||||
|
setSidebarOpen(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Submit Button Logic
|
// Submit Button Logic
|
||||||
const submitForm = async (event: React.FormEvent<HTMLFormElement>) => {
|
const submitForm = async (event: React.FormEvent<HTMLFormElement>) => {
|
||||||
// Preventing the page from reloading
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
const response = await fetch(`https://nominatim.openstreetmap.org/search?q=${term}&format=json&polygon=1&addressdetails=1`);
|
const response = await fetch(`https://nominatim.openstreetmap.org/search?q=${term}&format=json&polygon=1&addressdetails=1`);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
console.log(json);
|
|
||||||
setAddress(json);
|
setAddress(json);
|
||||||
console.log(address);
|
|
||||||
setSearchCompleted(true);
|
setSearchCompleted(true);
|
||||||
|
|
||||||
// Initialize all layers as visible
|
|
||||||
const initialVisibility = Object.fromEntries(
|
const initialVisibility = Object.fromEntries(
|
||||||
layerConfigs.map(layer => [layer.topic, true])
|
layerConfigs.map(layer => [layer.topic, true])
|
||||||
);
|
);
|
||||||
setLayerVisibility(initialVisibility);
|
setLayerVisibility(initialVisibility);
|
||||||
|
|
||||||
alert(term);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapDivRef = useRef<HTMLDivElement>(null);
|
const mapDivRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
@ -50,11 +59,11 @@ const OpenLayersMap = () => {
|
||||||
const toggleLayer = (layerTopic: string) => {
|
const toggleLayer = (layerTopic: string) => {
|
||||||
const layerRef = layerRefs.current[layerTopic];
|
const layerRef = layerRefs.current[layerTopic];
|
||||||
const isCurrentlyVisible = layerVisibility[layerTopic];
|
const isCurrentlyVisible = layerVisibility[layerTopic];
|
||||||
|
|
||||||
if (layerRef.current) {
|
if (layerRef.current) {
|
||||||
layerRef.current.setVisible(!isCurrentlyVisible);
|
layerRef.current.setVisible(!isCurrentlyVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayerVisibility(prev => ({
|
setLayerVisibility(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[layerTopic]: !isCurrentlyVisible
|
[layerTopic]: !isCurrentlyVisible
|
||||||
|
|
@ -88,7 +97,7 @@ const OpenLayersMap = () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
markerLayerRef.current = addMarker(coords, mapRef.current!, markerLayerRef.current);
|
markerLayerRef.current = addMarker(coords, mapRef.current!, markerLayerRef.current);
|
||||||
|
|
||||||
layerConfigs.forEach((layerConfig) => {
|
layerConfigs.forEach((layerConfig) => {
|
||||||
const url = layerConfig.getUrl(lon, lat, layerConfig.radius);
|
const url = layerConfig.getUrl(lon, lat, layerConfig.radius);
|
||||||
const ref = layerRefs.current[layerConfig.topic];
|
const ref = layerRefs.current[layerConfig.topic];
|
||||||
|
|
@ -102,171 +111,535 @@ const OpenLayersMap = () => {
|
||||||
}
|
}
|
||||||
}, [address]);
|
}, [address]);
|
||||||
|
|
||||||
// UI
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', height: '100vh', width: '100vw' }}>
|
<div style={{
|
||||||
{/* Sidebar */}
|
display: 'flex',
|
||||||
<div style={{
|
flexDirection: 'column',
|
||||||
width: '320px',
|
height: '100vh',
|
||||||
backgroundColor: '#f8f9fa',
|
width: '100vw',
|
||||||
borderRight: '1px solid #dee2e6',
|
fontFamily: "'Poppins', sans-serif",
|
||||||
padding: '20px',
|
backgroundColor: '#f8f9fa',
|
||||||
overflowY: 'auto',
|
overflow: 'hidden'
|
||||||
boxShadow: '2px 0 5px rgba(0,0,0,0.1)'
|
}}>
|
||||||
|
{/* Google Fonts Link */}
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
/* Fix for black borders */
|
||||||
|
body, html, #root {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Map container fixes */
|
||||||
|
.map-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Layer list improvements */
|
||||||
|
.layer-item {
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 2px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-item:hover {
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
/* Custom scrollbar */
|
||||||
|
.custom-scrollbar::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb {
|
||||||
|
background: #c1c1c1;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-scrollbar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #a1a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(20px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive styles */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.sidebar {
|
||||||
|
position: absolute !important;
|
||||||
|
width: 280px !important;
|
||||||
|
left: ${sidebarOpen ? '0' : '-280px'};
|
||||||
|
z-index: 1000;
|
||||||
|
transition: left 0.3s ease;
|
||||||
|
height: calc(100vh - 72px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.map-container {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-buttons {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-menu-btn {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content {
|
||||||
|
padding: 0 16px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
padding: 16px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.header-title {
|
||||||
|
font-size: 18px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 260px !important;
|
||||||
|
left: ${sidebarOpen ? '0' : '-260px'};
|
||||||
|
}
|
||||||
|
|
||||||
|
.coordinate-display {
|
||||||
|
bottom: 16px !important;
|
||||||
|
left: 16px !important;
|
||||||
|
right: 16px !important;
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{/* Header */}
|
||||||
|
<header style={{
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderBottom: '1px solid #e9ecef',
|
||||||
|
boxShadow: '0 2px 12px rgba(0, 0, 0, 0.08)',
|
||||||
|
padding: '0 32px',
|
||||||
|
height: '72px',
|
||||||
|
minHeight: '72px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1000,
|
||||||
|
flexShrink: 0
|
||||||
}}>
|
}}>
|
||||||
<h3 style={{
|
<div style={{
|
||||||
margin: '0 0 20px 0',
|
display: 'flex',
|
||||||
color: '#333',
|
alignItems: 'center',
|
||||||
fontSize: '18px',
|
gap: '12px'
|
||||||
fontWeight: '600'
|
|
||||||
}}>
|
}}>
|
||||||
Map Layers
|
{/* Mobile menu button - hidden on desktop */}
|
||||||
</h3>
|
<button
|
||||||
|
className="mobile-menu-btn"
|
||||||
{/* Search Form */}
|
onClick={() => setSidebarOpen(!sidebarOpen)}
|
||||||
<form onSubmit={submitForm} style={{ marginBottom: '20px' }}>
|
|
||||||
<input
|
|
||||||
value={term}
|
|
||||||
onChange={(e) => setTerm(e.target.value)}
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter address to search"
|
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
display: 'none',
|
||||||
padding: '10px',
|
background: 'none',
|
||||||
border: '1px solid #ddd',
|
|
||||||
borderRadius: '4px',
|
|
||||||
marginBottom: '10px',
|
|
||||||
fontSize: '14px'
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
style={{
|
|
||||||
width: '100%',
|
|
||||||
padding: '10px',
|
|
||||||
backgroundColor: '#007bff',
|
|
||||||
color: 'white',
|
|
||||||
border: 'none',
|
border: 'none',
|
||||||
borderRadius: '4px',
|
fontSize: '20px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
fontSize: '14px',
|
padding: '8px',
|
||||||
fontWeight: '500'
|
color: '#2c3e50'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Search
|
☰
|
||||||
</button>
|
</button>
|
||||||
</form>
|
|
||||||
|
|
||||||
{/* Layer Toggle List */}
|
<div style={{
|
||||||
<div>
|
width: '40px',
|
||||||
<h4 style={{
|
height: '40px',
|
||||||
margin: '0 0 15px 0',
|
background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)',
|
||||||
color: '#666',
|
borderRadius: '12px',
|
||||||
fontSize: '14px',
|
display: 'flex',
|
||||||
fontWeight: '500',
|
alignItems: 'center',
|
||||||
textTransform: 'uppercase',
|
justifyContent: 'center',
|
||||||
letterSpacing: '0.5px'
|
boxShadow: '0 4px 12px rgba(44, 62, 80, 0.15)'
|
||||||
}}>
|
}}>
|
||||||
Available Layers ({layerConfigs.length})
|
<span style={{
|
||||||
</h4>
|
color: 'white',
|
||||||
|
fontSize: '18px',
|
||||||
{layerConfigs.map((layer) => (
|
fontWeight: '700'
|
||||||
<div key={layer.topic} style={{
|
}}>M</span>
|
||||||
|
</div>
|
||||||
|
<span className="header-title" style={{
|
||||||
|
fontSize: '24px',
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#2c3e50',
|
||||||
|
letterSpacing: '-0.5px'
|
||||||
|
}}>
|
||||||
|
MapExplorer
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navigation - hidden on mobile */}
|
||||||
|
<nav className="nav-buttons" style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
|
{['Home', 'Map', 'About', 'Contact'].map((item, index) => (
|
||||||
|
<button
|
||||||
|
key={item}
|
||||||
|
style={{
|
||||||
|
padding: '12px 20px',
|
||||||
|
backgroundColor: index === 1 ? '#2c3e50' : 'transparent',
|
||||||
|
color: index === 1 ? 'white' : '#495057',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '12px',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '500',
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
boxShadow: index === 1 ? '0 4px 12px rgba(44, 62, 80, 0.2)' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '16px'
|
||||||
|
}}>
|
||||||
|
<button style={{
|
||||||
|
padding: '10px',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '10px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
color: '#6c757d',
|
||||||
|
fontSize: '16px',
|
||||||
|
transition: 'all 0.3s ease',
|
||||||
|
}}>
|
||||||
|
🔔
|
||||||
|
</button>
|
||||||
|
<div style={{
|
||||||
|
width: '36px',
|
||||||
|
height: '36px',
|
||||||
|
background: 'linear-gradient(135deg, #6c757d 0%, #8b949e 100%)',
|
||||||
|
borderRadius: '50%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
color: 'white',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
cursor: 'pointer',
|
||||||
|
boxShadow: '0 3px 8px rgba(108, 117, 125, 0.15)',
|
||||||
|
}}>
|
||||||
|
U
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
{/* Main Content */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
height: 'calc(100vh - 72px)',
|
||||||
|
width: '100vw',
|
||||||
|
overflow: 'hidden',
|
||||||
|
position: 'relative'
|
||||||
|
}}>
|
||||||
|
{/* Sidebar with responsive behavior */}
|
||||||
|
<div className="sidebar custom-scrollbar" style={{
|
||||||
|
width: '360px',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderRight: '1px solid #e9ecef',
|
||||||
|
padding: '24px',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden',
|
||||||
|
boxShadow: '4px 0 16px rgba(0,0,0,0.06)',
|
||||||
|
flexShrink: 0
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
marginBottom: '24px'
|
||||||
|
}}>
|
||||||
|
<div style={{
|
||||||
|
width: '8px',
|
||||||
|
height: '32px',
|
||||||
|
background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)',
|
||||||
|
borderRadius: '4px'
|
||||||
|
}} />
|
||||||
|
<h3 style={{
|
||||||
|
margin: '0',
|
||||||
|
color: '#2c3e50',
|
||||||
|
fontSize: '20px',
|
||||||
|
fontWeight: '600',
|
||||||
|
letterSpacing: '-0.3px'
|
||||||
|
}}>
|
||||||
|
Map Controls
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Search Form */}
|
||||||
|
<div className="search-container" style={{
|
||||||
|
background: 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)',
|
||||||
|
padding: '20px',
|
||||||
|
borderRadius: '16px',
|
||||||
|
marginBottom: '24px',
|
||||||
|
border: '1px solid #e9ecef',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.04)'
|
||||||
|
}}>
|
||||||
|
<form onSubmit={submitForm}>
|
||||||
|
<input
|
||||||
|
value={term}
|
||||||
|
onChange={(e) => setTerm(e.target.value)}
|
||||||
|
type="text"
|
||||||
|
placeholder="Search for any location..."
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '14px 16px',
|
||||||
|
border: '1px solid #dee2e6',
|
||||||
|
borderRadius: '12px',
|
||||||
|
marginBottom: '12px',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontFamily: "'Poppins', sans-serif",
|
||||||
|
backgroundColor: 'white',
|
||||||
|
boxShadow: '0 2px 4px rgba(0,0,0,0.04)',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
outline: 'none'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
padding: '14px',
|
||||||
|
background: 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)',
|
||||||
|
color: 'white',
|
||||||
|
border: 'none',
|
||||||
|
borderRadius: '12px',
|
||||||
|
cursor: 'pointer',
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: '600',
|
||||||
|
fontFamily: "'Poppins', sans-serif",
|
||||||
|
boxShadow: '0 4px 12px rgba(44, 62, 80, 0.2)',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
🔍 Search Location
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Layer Toggle List */}
|
||||||
|
<div>
|
||||||
|
<div style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
padding: '10px 0',
|
marginBottom: '16px'
|
||||||
borderBottom: '1px solid #eee'
|
|
||||||
}}>
|
}}>
|
||||||
<span style={{
|
<h4 style={{
|
||||||
fontSize: '13px',
|
margin: '0',
|
||||||
color: '#333',
|
color: '#495057',
|
||||||
flex: 1,
|
fontSize: '16px',
|
||||||
marginRight: '10px'
|
fontWeight: '600'
|
||||||
}}>
|
}}>
|
||||||
{formatLayerName(layer.topic)}
|
Data Layers
|
||||||
|
</h4>
|
||||||
|
<span style={{
|
||||||
|
background: 'linear-gradient(135deg, #e9ecef 0%, #f8f9fa 100%)',
|
||||||
|
color: '#6c757d',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
fontSize: '12px',
|
||||||
|
fontWeight: '500',
|
||||||
|
boxShadow: '0 1px 3px rgba(0,0,0,0.05)'
|
||||||
|
}}>
|
||||||
|
{layerConfigs.length}
|
||||||
</span>
|
</span>
|
||||||
|
</div>
|
||||||
{searchCompleted && (
|
|
||||||
<label style={{
|
{layerConfigs.map((layer, _index) => (
|
||||||
position: 'relative',
|
<div key={layer.topic} style={{
|
||||||
display: 'inline-block',
|
display: 'flex',
|
||||||
width: '44px',
|
alignItems: 'center',
|
||||||
height: '24px'
|
justifyContent: 'space-between',
|
||||||
|
padding: '16px',
|
||||||
|
marginBottom: '8px',
|
||||||
|
background: layerVisibility[layer.topic]
|
||||||
|
? 'linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%)'
|
||||||
|
: 'white',
|
||||||
|
border: `1px solid ${layerVisibility[layer.topic] ? '#2c3e50' : '#e9ecef'}`,
|
||||||
|
borderRadius: '12px',
|
||||||
|
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
boxShadow: layerVisibility[layer.topic]
|
||||||
|
? '0 4px 8px rgba(44, 62, 80, 0.1)'
|
||||||
|
: '0 1px 3px rgba(0,0,0,0.05)',
|
||||||
|
cursor: 'pointer'
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#2c3e50',
|
||||||
|
flex: 1,
|
||||||
|
marginRight: '12px',
|
||||||
|
fontWeight: '500'
|
||||||
}}>
|
}}>
|
||||||
<input
|
{formatLayerName(layer.topic)}
|
||||||
type="checkbox"
|
</span>
|
||||||
checked={layerVisibility[layer.topic] || false}
|
|
||||||
onChange={() => toggleLayer(layer.topic)}
|
{searchCompleted && (
|
||||||
style={{ opacity: 0, width: 0, height: 0 }}
|
<label style={{
|
||||||
/>
|
position: 'relative',
|
||||||
<span style={{
|
display: 'inline-block',
|
||||||
position: 'absolute',
|
width: '48px',
|
||||||
cursor: 'pointer',
|
height: '26px'
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
backgroundColor: layerVisibility[layer.topic] ? '#007bff' : '#ccc',
|
|
||||||
borderRadius: '24px',
|
|
||||||
transition: '0.3s',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}>
|
}}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={layerVisibility[layer.topic] || false}
|
||||||
|
onChange={() => toggleLayer(layer.topic)}
|
||||||
|
style={{ opacity: 0, width: 0, height: 0 }}
|
||||||
|
/>
|
||||||
<span style={{
|
<span style={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
content: '',
|
cursor: 'pointer',
|
||||||
height: '18px',
|
top: 0,
|
||||||
width: '18px',
|
left: 0,
|
||||||
left: layerVisibility[layer.topic] ? '23px' : '3px',
|
right: 0,
|
||||||
bottom: '3px',
|
bottom: 0,
|
||||||
backgroundColor: 'white',
|
background: layerVisibility[layer.topic]
|
||||||
borderRadius: '50%',
|
? 'linear-gradient(135deg, #2c3e50 0%, #34495e 100%)'
|
||||||
transition: '0.3s',
|
: '#dee2e6',
|
||||||
boxShadow: '0 2px 4px rgba(0,0,0,0.2)'
|
borderRadius: '26px',
|
||||||
}} />
|
transition: '0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
boxShadow: 'inset 0 1px 3px rgba(0,0,0,0.1)'
|
||||||
|
}}>
|
||||||
|
<span style={{
|
||||||
|
position: 'absolute',
|
||||||
|
content: '',
|
||||||
|
height: '20px',
|
||||||
|
width: '20px',
|
||||||
|
left: layerVisibility[layer.topic] ? '25px' : '3px',
|
||||||
|
bottom: '3px',
|
||||||
|
backgroundColor: 'white',
|
||||||
|
borderRadius: '50%',
|
||||||
|
transition: '0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
boxShadow: '0 2px 6px rgba(0,0,0,0.15)'
|
||||||
|
}} />
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!searchCompleted && (
|
||||||
|
<span style={{
|
||||||
|
fontSize: '12px',
|
||||||
|
color: '#adb5bd',
|
||||||
|
fontStyle: 'italic',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
padding: '4px 8px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
boxShadow: '0 1px 2px rgba(0,0,0,0.05)'
|
||||||
|
}}>
|
||||||
|
Search first
|
||||||
</span>
|
</span>
|
||||||
</label>
|
)}
|
||||||
)}
|
</div>
|
||||||
|
))}
|
||||||
{!searchCompleted && (
|
|
||||||
<span style={{
|
|
||||||
fontSize: '11px',
|
|
||||||
color: '#999',
|
|
||||||
fontStyle: 'italic'
|
|
||||||
}}>
|
|
||||||
Search first
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Map Container */}
|
|
||||||
<div style={{ flex: 1, position: 'relative' }}>
|
|
||||||
<div ref={mapDivRef} style={{ width: '100%', height: '100%' }} />
|
|
||||||
|
|
||||||
{clickedCoordinate && (
|
|
||||||
<div style={{
|
|
||||||
position: 'absolute',
|
|
||||||
bottom: '20px',
|
|
||||||
left: '20px',
|
|
||||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
|
||||||
color: 'white',
|
|
||||||
padding: '10px 15px',
|
|
||||||
borderRadius: '4px',
|
|
||||||
fontSize: '12px',
|
|
||||||
fontFamily: 'monospace'
|
|
||||||
}}>
|
|
||||||
Coordinates: {clickedCoordinate[0].toFixed(2)} / {clickedCoordinate[1].toFixed(2)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
|
|
||||||
|
{/* Map Container */}
|
||||||
|
<div className="map-container" style={{
|
||||||
|
flex: 1,
|
||||||
|
position: 'relative',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
}}>
|
||||||
|
{/* Overlay for mobile when sidebar is open */}
|
||||||
|
{isMobile && sidebarOpen && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||||
|
zIndex: 999,
|
||||||
|
transition: 'opacity 0.3s ease'
|
||||||
|
}}
|
||||||
|
onClick={() => setSidebarOpen(false)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div ref={mapDivRef} style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
borderRadius: '0',
|
||||||
|
overflow: 'hidden'
|
||||||
|
}} />
|
||||||
|
|
||||||
|
{/* Coordinate Display */}
|
||||||
|
{clickedCoordinate && (
|
||||||
|
<div className="coordinate-display" style={{
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '24px',
|
||||||
|
left: '24px',
|
||||||
|
background: 'rgba(44, 62, 80, 0.9)',
|
||||||
|
color: 'white',
|
||||||
|
padding: '12px 16px',
|
||||||
|
borderRadius: '12px',
|
||||||
|
fontSize: '13px',
|
||||||
|
fontFamily: "'Poppins', monospace",
|
||||||
|
fontWeight: '500',
|
||||||
|
boxShadow: '0 8px 32px rgba(44, 62, 80, 0.3)',
|
||||||
|
backdropFilter: 'blur(10px)',
|
||||||
|
border: '1px solid rgba(255, 255, 255, 0.1)',
|
||||||
|
animation: 'fadeInUp 0.3s ease-out'
|
||||||
|
}}>
|
||||||
|
📍 Coordinates: {clickedCoordinate[0].toFixed(4)}, {clickedCoordinate[1].toFixed(4)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default OpenLayersMap;
|
export default OpenLayersMap;
|
||||||
Loading…
Reference in a new issue