Compare commits

...

3 commits

3 changed files with 42 additions and 71 deletions

View file

@ -19,76 +19,76 @@ const baseConfigs: Omit<LayerConfig, 'getUrl'>[] = [
radius: 1, radius: 1,
topic: 'Parcels', topic: 'Parcels',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'green', width: 1.5 }), stroke: new Stroke({ color: '#00D9FF', width: 5.5 }),
fill: new Fill({ color: 'rgba(0, 255, 0, 0.1)' }), fill: new Fill({ color: 'rgba(0, 217, 255, 0.1)' }),
}), }),
}, },
{ {
radius: 100, radius: 100,
topic: 'Sewer_Main_SD', topic: 'Sewer_Main_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'purple', width: 2 }), stroke: new Stroke({ color: '#FF00FF', width: 2 }),
}), }),
}, },
{ {
radius: 50, radius: 50,
topic: 'Zoning_Base_SD', topic: 'Zoning_Base_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'red', width: 1 }), stroke: new Stroke({ color: '#FF3333', width: 1 }),
}), }),
}, },
{ {
radius: 50, radius: 50,
topic: 'Water_Main_SD', topic: 'Water_Main_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'blue', width: 5 }), stroke: new Stroke({ color: '#0066FF', width: 5 }),
}), }),
}, },
{ {
radius: 1000, radius: 1000,
topic: 'Sustainable_Development_Areas', topic: 'Sustainable_Development_Areas',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'green', width: 3 }), stroke: new Stroke({ color: '#00FF66', width: 3 }),
}), }),
}, },
{ {
radius: 1000, radius: 1000,
topic: 'Housing_Solution_Areas', topic: 'Housing_Solution_Areas',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'black', width: 2.5 }), stroke: new Stroke({ color: '#FFD700', width: 2.5 }),
fill: new Fill({ color: 'rgba(255, 200, 0, 0.1)' }), fill: new Fill({ color: 'rgba(255, 215, 0, 0.1)' }),
}), }),
}, },
{ {
radius: 100000, radius: 100000,
topic: 'Promise_Zones_SD', topic: 'Promise_Zones_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'black', width: 2.5 }), stroke: new Stroke({ color: '#9933FF', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(153, 51, 255, 0.1)' }),
}), }),
}, },
{ {
radius: 100000, radius: 100000,
topic: 'MHPA_SD', topic: 'MHPA_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'orange', width: 2.5 }), stroke: new Stroke({ color: '#FF6B6B', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(255, 107, 107, 0.1)' }),
}), }),
}, },
{ {
radius: 100000, radius: 100000,
topic: 'Coastal_Zones', topic: 'Coastal_Zones',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'skin', width: 2.5 }), stroke: new Stroke({ color: '#FF9966', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(255, 153, 102, 0.1)' }),
}), }),
}, },
{ {
radius: 100000, radius: 100000,
topic: 'Community_Plan_Implementation_Overlay_Zone_SD', topic: 'Community_Plan_Implementation_Overlay_Zone_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'darkblue', width: 2.5 }), stroke: new Stroke({ color: '#4169E1', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(65, 105, 225, 0.1)' }),
}), }),
}, },
@ -96,8 +96,8 @@ const baseConfigs: Omit<LayerConfig, 'getUrl'>[] = [
radius: 100000, radius: 100000,
topic: 'Airport_Influence_Area', topic: 'Airport_Influence_Area',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'darkyellow', width: 2.5 }), stroke: new Stroke({ color: '#FFBB00', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(255, 187, 0, 0.1)' }),
}), }),
}, },
@ -105,8 +105,8 @@ const baseConfigs: Omit<LayerConfig, 'getUrl'>[] = [
radius: 100000, radius: 100000,
topic: 'Historic_Districts_SD', topic: 'Historic_Districts_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'white', width: 2.5 }), stroke: new Stroke({ color: '#C0C0C0', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(192, 192, 192, 0.1)' }),
}), }),
}, },
@ -114,8 +114,8 @@ const baseConfigs: Omit<LayerConfig, 'getUrl'>[] = [
radius: 100000, radius: 100000,
topic: 'HRB_Designated_Resources_SD', topic: 'HRB_Designated_Resources_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'darkgreen', width: 2.5 }), stroke: new Stroke({ color: '#00CC66', width: 2.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(0, 204, 102, 0.1)' }),
}), }),
}, },
@ -123,8 +123,8 @@ const baseConfigs: Omit<LayerConfig, 'getUrl'>[] = [
radius: 100000, radius: 100000,
topic: 'Fire_Hazard_Severity_Zones_SD', topic: 'Fire_Hazard_Severity_Zones_SD',
style: new Style({ style: new Style({
stroke: new Stroke({ color: 'yellow', width: 1.5 }), stroke: new Stroke({ color: '#FF6600', width: 1.5 }),
fill: new Fill({ color: 'rgba(116, 3, 255, 0.1)' }), fill: new Fill({ color: 'rgba(255, 102, 0, 0.1)' }),
}), }),
}, },

View file

@ -1,4 +1,3 @@
// src/Methods/addMarker.ts
import Feature from 'ol/Feature'; import Feature from 'ol/Feature';
import Point from 'ol/geom/Point'; import Point from 'ol/geom/Point';
@ -9,16 +8,11 @@ import Icon from 'ol/style/Icon';
import { Map } from 'ol'; import { Map } from 'ol';
import { Coordinate } from 'ol/coordinate'; import { Coordinate } from 'ol/coordinate';
/**
* Adds a custom icon marker to the map at a given coordinate,
* replacing the previous marker layer if one exists.
*/
export const addMarker = ( export const addMarker = (
coords: Coordinate, coords: Coordinate,
map: Map, map: Map,
existingMarkerLayer: VectorLayer | null existingMarkerLayer: VectorLayer | null
): VectorLayer => { ): VectorLayer => {
// Create feature for the marker
const markerFeature = new Feature({ const markerFeature = new Feature({
geometry: new Point(coords), geometry: new Point(coords),
}); });

View file

@ -13,16 +13,15 @@ import gsap from 'gsap';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { easeOut } from 'ol/easing'; import { easeOut } from 'ol/easing';
// --- THEME CONFIGURATION ---
const themes = { const themes = {
dark: { dark: {
bg: '#050505', bg: '#050505',
glass: 'rgba(20, 20, 20, 0.75)', // Slightly darker for better contrast glass: 'rgba(20, 20, 20, 0.75)',
border: 'rgba(255, 255, 255, 0.15)', // Stronger border border: 'rgba(255, 255, 255, 0.15)',
textMain: '#ffffff', textMain: '#ffffff',
textMuted: '#AAAAAA', textMuted: '#AAAAAA',
iconColor: '#ffffff', iconColor: '#ffffff',
shadow: '0 4px 20px rgba(0,0,0,0.4)', // Strong shadow shadow: '0 4px 20px rgba(0,0,0,0.4)',
switchTrackActive: 'rgba(255,255,255,0.2)', switchTrackActive: 'rgba(255,255,255,0.2)',
switchTrackInactive: 'rgba(255,255,255,0.05)', switchTrackInactive: 'rgba(255,255,255,0.05)',
switchKnobActive: '#fff', switchKnobActive: '#fff',
@ -31,7 +30,7 @@ const themes = {
}, },
light: { light: {
bg: '#F4F4F5', bg: '#F4F4F5',
glass: 'rgba(255, 255, 255, 0.90)', // More opaque for visibility glass: 'rgba(255, 255, 255, 0.90)',
border: 'rgba(0, 0, 0, 0.1)', border: 'rgba(0, 0, 0, 0.1)',
textMain: '#18181B', textMain: '#18181B',
textMuted: '#52525B', textMuted: '#52525B',
@ -45,7 +44,6 @@ const themes = {
} }
}; };
// --- SCROLLBAR CSS ---
const scrollbarStyles = ` const scrollbarStyles = `
.custom-scrollbar::-webkit-scrollbar { width: 4px; } .custom-scrollbar::-webkit-scrollbar { width: 4px; }
.custom-scrollbar::-webkit-scrollbar-track { background: rgba(128, 128, 128, 0.1); border-radius: 4px; } .custom-scrollbar::-webkit-scrollbar-track { background: rgba(128, 128, 128, 0.1); border-radius: 4px; }
@ -53,18 +51,14 @@ const scrollbarStyles = `
.custom-scrollbar::-webkit-scrollbar-thumb:hover { background: rgba(128, 128, 128, 0.5); } .custom-scrollbar::-webkit-scrollbar-thumb:hover { background: rgba(128, 128, 128, 0.5); }
`; `;
// --- HELPER: Extract Color ---
const extractColorFromStyle = (style: any): string => { const extractColorFromStyle = (style: any): string => {
let color: any = '#888888'; if (style.getStroke() && style.getStroke().getColor()) {
if (style.getStroke() && style.getStroke().getColor()) color = style.getStroke().getColor(); return style.getStroke().getColor();
else if (style.getFill() && style.getFill().getColor()) color = style.getFill().getColor(); }
if (style.getFill() && style.getFill().getColor()) {
const colorMap: Record<string, string> = { return style.getFill().getColor();
'skin': '#FFCBA4', 'darkyellow': '#FCC200', 'darkblue': '#00008B', }
'darkgreen': '#006400', 'purple': '#800080', 'orange': '#FFA500', return '#888888';
'green': '#00FF00', 'blue': '#0000FF', 'red': '#FF0000', 'black': '#888888',
};
return colorMap[color] || color;
}; };
interface MapViewProps { interface MapViewProps {
@ -75,7 +69,6 @@ interface MapViewProps {
const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) => { const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) => {
const navigate = useNavigate(); const navigate = useNavigate();
// -- STATE --
const [isDarkMode, setIsDarkMode] = useState(true); const [isDarkMode, setIsDarkMode] = useState(true);
const currentTheme = isDarkMode ? themes.dark : themes.light; const currentTheme = isDarkMode ? themes.dark : themes.light;
@ -90,7 +83,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
size: { width: 0, height: 0 } size: { width: 0, height: 0 }
}); });
// -- REFS --
const mapDivRef = useRef<HTMLDivElement>(null); const mapDivRef = useRef<HTMLDivElement>(null);
const mapRef = useRef<Map | null>(null); const mapRef = useRef<Map | null>(null);
const tileLayerRef = useRef<TileLayer<XYZ> | null>(null); const tileLayerRef = useRef<TileLayer<XYZ> | null>(null);
@ -100,10 +92,8 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
); );
const uiContainerRef = useRef<HTMLDivElement>(null); const uiContainerRef = useRef<HTMLDivElement>(null);
// -- GSAP ENTRANCE --
useLayoutEffect(() => { useLayoutEffect(() => {
const ctx = gsap.context(() => { const ctx = gsap.context(() => {
// We use clearProps: 'all' to ensure GSAP doesn't leave the elements with weird opacity states
gsap.from('.floating-ui', { gsap.from('.floating-ui', {
y: 20, y: 20,
opacity: 0, opacity: 0,
@ -111,13 +101,12 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
stagger: 0.1, stagger: 0.1,
ease: 'power3.out', ease: 'power3.out',
delay: 0.5, delay: 0.5,
clearProps: 'opacity,transform' // Crucial: Ensures visibility after animation ends clearProps: 'opacity,transform'
}); });
}, uiContainerRef); }, uiContainerRef);
return () => ctx.revert(); return () => ctx.revert();
}, []); }, []);
// -- MAP INIT --
useEffect(() => { useEffect(() => {
if (!mapRef.current) { if (!mapRef.current) {
const baseLayer = new TileLayer({ const baseLayer = new TileLayer({
@ -154,7 +143,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
} }
}, []); }, []);
// -- THEME SWITCHING --
useEffect(() => { useEffect(() => {
if (mapRef.current && tileLayerRef.current) { if (mapRef.current && tileLayerRef.current) {
tileLayerRef.current.setSource(new XYZ({ tileLayerRef.current.setSource(new XYZ({
@ -164,7 +152,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
} }
}, [isDarkMode]); }, [isDarkMode]);
// -- SEARCH DATA --
useEffect(() => { useEffect(() => {
if (initialAddressData && initialAddressData.length > 0) { if (initialAddressData && initialAddressData.length > 0) {
const lat = parseFloat(initialAddressData[0].lat); const lat = parseFloat(initialAddressData[0].lat);
@ -196,7 +183,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
const clickedLonLat = clickedCoordinate ? toLonLat(clickedCoordinate) : null; const clickedLonLat = clickedCoordinate ? toLonLat(clickedCoordinate) : null;
const activeLayerCount = Object.values(layerVisibility).filter(Boolean).length; const activeLayerCount = Object.values(layerVisibility).filter(Boolean).length;
// -- STYLES --
const styles = { const styles = {
wrapper: { wrapper: {
position: 'relative' as const, width: '100vw', height: '100vh', position: 'relative' as const, width: '100vw', height: '100vh',
@ -207,7 +193,7 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
uiLayer: { uiLayer: {
position: 'absolute' as const, top: 0, left: 0, width: '100%', height: '100%', position: 'absolute' as const, top: 0, left: 0, width: '100%', height: '100%',
pointerEvents: 'none' as const, pointerEvents: 'none' as const,
zIndex: 1000, // FORCE TOP LAYER zIndex: 1000,
padding: '24px', padding: '24px',
display: 'flex', flexDirection: 'column' as const, justifyContent: 'space-between', display: 'flex', flexDirection: 'column' as const, justifyContent: 'space-between',
}, },
@ -216,7 +202,7 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
alignItems: 'center', alignItems: 'center',
pointerEvents: 'auto' as const, pointerEvents: 'auto' as const,
gap: '12px', gap: '12px',
width: '100%' // Ensure it spans width width: '100%'
}, },
glassPanel: { glassPanel: {
background: currentTheme.glass, background: currentTheme.glass,
@ -232,7 +218,7 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
backdropFilter: 'blur(20px)', border: `1px solid ${currentTheme.border}`, backdropFilter: 'blur(20px)', border: `1px solid ${currentTheme.border}`,
boxShadow: currentTheme.shadow, boxShadow: currentTheme.shadow,
transition: 'all 0.3s ease', transition: 'all 0.3s ease',
flexShrink: 0 // Prevent collapsing flexShrink: 0
}, },
actionButton: { actionButton: {
width: '48px', height: '48px', borderRadius: '50%', width: '48px', height: '48px', borderRadius: '50%',
@ -240,10 +226,10 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
background: currentTheme.glass, background: currentTheme.glass,
backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)', backdropFilter: 'blur(20px)', WebkitBackdropFilter: 'blur(20px)',
border: `1px solid ${currentTheme.border}`, border: `1px solid ${currentTheme.border}`,
color: currentTheme.iconColor, // Explicit high contrast color color: currentTheme.iconColor,
boxShadow: currentTheme.shadow, // Force visibility boxShadow: currentTheme.shadow,
cursor: 'pointer', transition: 'all 0.2s', cursor: 'pointer', transition: 'all 0.2s',
flexShrink: 0 // Prevent collapsing flexShrink: 0
}, },
controlsContainer: { controlsContainer: {
marginTop: 'auto', maxWidth: '340px', maxHeight: '60vh', marginTop: 'auto', maxWidth: '340px', maxHeight: '60vh',
@ -346,19 +332,15 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
<div style={styles.wrapper}> <div style={styles.wrapper}>
<style>{scrollbarStyles}</style> <style>{scrollbarStyles}</style>
{/* 1. MAP LAYER */}
<div <div
ref={mapDivRef} ref={mapDivRef}
style={{ width: '100%', height: '100%', transition: 'filter 0.5s' }} style={{ width: '100%', height: '100%', transition: 'filter 0.5s' }}
/> />
{/* 2. UI LAYER */}
<div ref={uiContainerRef} style={styles.uiLayer}> <div ref={uiContainerRef} style={styles.uiLayer}>
{/* TOP BAR */}
<div style={styles.topBar}> <div style={styles.topBar}>
{/* Back Button */}
<button <button
className="floating-ui" className="floating-ui"
style={styles.actionButton} style={styles.actionButton}
@ -370,7 +352,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>
</button> </button>
{/* Location Badge */}
<div className="floating-ui" style={styles.locationBadge}> <div className="floating-ui" style={styles.locationBadge}>
<div style={{ width: '6px', height: '6px', background: '#00FF94', borderRadius: '50%', marginRight: '12px', boxShadow: '0 0 8px #00FF94' }} /> <div style={{ width: '6px', height: '6px', background: '#00FF94', borderRadius: '50%', marginRight: '12px', boxShadow: '0 0 8px #00FF94' }} />
<span style={{ fontSize: '14px', fontWeight: 600, letterSpacing: '0.5px', color: currentTheme.textMain, textTransform: 'uppercase' }}> <span style={{ fontSize: '14px', fontWeight: 600, letterSpacing: '0.5px', color: currentTheme.textMain, textTransform: 'uppercase' }}>
@ -378,7 +359,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
</span> </span>
</div> </div>
{/* Theme Toggle Button */}
<button <button
className="floating-ui" className="floating-ui"
style={styles.actionButton} style={styles.actionButton}
@ -396,7 +376,6 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
</div> </div>
{/* CONTROLS */}
<div className="floating-ui" style={styles.controlsContainer}> <div className="floating-ui" style={styles.controlsContainer}>
<div style={styles.controlsHeader}>Overlay Controls</div> <div style={styles.controlsHeader}>Overlay Controls</div>
@ -428,14 +407,12 @@ const MapView: React.FC<MapViewProps> = ({ initialAddressData, initialTerm }) =>
</div> </div>
</div> </div>
{/* HUD */}
{clickedCoordinate && ( {clickedCoordinate && (
<div className="floating-ui" style={styles.hud}> <div className="floating-ui" style={styles.hud}>
LOCATION_LOCK: {clickedCoordinate[1].toFixed(4)} N, {clickedCoordinate[0].toFixed(4)} E LOCATION_LOCK: {clickedCoordinate[1].toFixed(4)} N, {clickedCoordinate[0].toFixed(4)} E
</div> </div>
)} )}
{/* RIGHT SIDEBAR */}
<div className="floating-ui" style={styles.rightSidebar}> <div className="floating-ui" style={styles.rightSidebar}>
<div style={styles.sidebarCard}> <div style={styles.sidebarCard}>
<div style={styles.sidebarTitle}>General Values</div> <div style={styles.sidebarTitle}>General Values</div>