// Daoob AI — Riyadh map component // Stylized SVG representation of Riyadh with selectable parcels. const { useState, useRef, useEffect, useMemo } = React; // Major roads as SVG paths (in 1000x700 space) const ROADS = { primary: [ // Northern Ring { d: "M 40 80 L 960 80", name: { en: "Northern Ring", ar: "الطريق الدائري الشمالي" }, lblX: 500, lblY: 72 }, // Eastern Ring { d: "M 880 60 L 880 640", name: { en: "Eastern Ring", ar: "الدائري الشرقي" }, lblX: 894, lblY: 350, rot: -90 }, // Western Ring { d: "M 90 90 L 90 580", name: { en: "Western Ring", ar: "الدائري الغربي" }, lblX: 76, lblY: 340, rot: -90 }, // Southern Ring { d: "M 80 580 L 900 580", name: { en: "Southern Ring", ar: "الدائري الجنوبي" }, lblX: 500, lblY: 596 }, // King Fahd Rd (vertical, center) { d: "M 500 80 L 500 580", name: { en: "King Fahd Rd", ar: "طريق الملك فهد" }, lblX: 488, lblY: 500, rot: -90 }, // King Abdullah Rd (horizontal mid-north) { d: "M 90 220 L 880 220", name: { en: "King Abdullah Rd", ar: "طريق الملك عبدالله" }, lblX: 200, lblY: 214 }, // Makkah Rd (horizontal mid-south) { d: "M 90 460 L 880 460", name: { en: "Makkah Rd", ar: "طريق مكة" }, lblX: 200, lblY: 454 }, // Khurais Rd { d: "M 500 380 L 880 380", name: { en: "Khurais Rd", ar: "طريق خريص" }, lblX: 760, lblY: 374 }, ], secondary: [ // Olaya St (parallel to King Fahd, west) "M 460 80 L 460 580", "M 440 80 L 440 580", // Tahlia / Prince Mohammed "M 90 320 L 500 320", // Prince Sultan Rd "M 90 380 L 500 380", // Imam Saud bin Faisal "M 90 160 L 880 160", // North-eastern grid "M 600 80 L 600 580", "M 700 80 L 700 580", "M 800 80 L 800 580", "M 200 80 L 200 580", "M 300 80 L 300 580", // East-west grid "M 90 280 L 880 280", "M 90 420 L 880 420", "M 90 520 L 880 520", "M 90 120 L 880 120", ], tertiary: [ // Fine grid ...Array.from({ length: 20 }, (_, i) => `M ${110 + i * 40} 80 L ${110 + i * 40} 580`), ...Array.from({ length: 12 }, (_, i) => `M 90 ${100 + i * 40} L 880 ${100 + i * 40}`), ], }; // District label positions const DISTRICT_LABELS = [ { id: "olaya", x: 490, y: 296 }, { id: "sulaymaniyah", x: 410, y: 258 }, { id: "malaz", x: 586, y: 428 }, { id: "diplomatic", x: 244, y: 250 }, { id: "malqa", x: 378, y: 148 }, { id: "hittin", x: 306, y: 164 }, { id: "yasmin", x: 598, y: 154 }, { id: "kafd", x: 492, y: 184 }, { id: "naseem", x: 778, y: 424 }, { id: "rawdah", x: 704, y: 364 }, { id: "irqah", x: 172, y: 328 }, { id: "nakheel", x: 450, y: 362 }, ]; // Build a polygon path for a parcel rect with slight irregularity function parcelPath(p) { const { x, y, w, h, id } = p; // deterministic jitter from id const seed = id.charCodeAt(id.length - 1); const j = (n) => ((seed * n) % 7) - 3; return [ `M ${x + j(1)} ${y + j(2)}`, `L ${x + w + j(3)} ${y + j(4)}`, `L ${x + w + j(5)} ${y + h + j(6)}`, `L ${x + j(7)} ${y + h + j(8)}`, "Z", ].join(" "); } const BASEMAP_STYLE = { street: { bg: "#0a3927", land: "#0b3f2c", roadPrimary: "rgba(232, 214, 168, 0.85)", roadSecondary: "rgba(232, 214, 168, 0.30)", roadTertiary: "rgba(232, 214, 168, 0.10)", label: "#E8D6A8" }, satellite: { bg: "#1a1e16", land: "#1f2520", roadPrimary: "rgba(255, 247, 220, 0.55)", roadSecondary: "rgba(255, 247, 220, 0.20)", roadTertiary: "rgba(255, 247, 220, 0.06)", label: "#F2E6C8" }, minimal: { bg: "#F6F2E8", land: "#EEEADD", roadPrimary: "rgba(11, 93, 59, 0.65)", roadSecondary: "rgba(11, 93, 59, 0.20)", roadTertiary: "rgba(11, 93, 59, 0.08)", label: "#063522" }, }; const RiyadhMap = ({ parcels, zoning, districts, selectedId, onSelect, basemap = "street", showZoning = true, showLabels = true, filterZones = null, // array of zone keys or null lang = "en", }) => { const [zoom, setZoom] = useState(1); const [pan, setPan] = useState({ x: 0, y: 0 }); const [hover, setHover] = useState(null); const dragRef = useRef(null); const style = BASEMAP_STYLE[basemap]; const filtered = useMemo(() => { if (!filterZones || filterZones.length === 0) return parcels; return parcels.filter(p => filterZones.includes(p.zone)); }, [parcels, filterZones]); const onMouseDown = (e) => { dragRef.current = { x: e.clientX, y: e.clientY, panX: pan.x, panY: pan.y }; }; const onMouseMove = (e) => { if (!dragRef.current) return; const dx = e.clientX - dragRef.current.x; const dy = e.clientY - dragRef.current.y; setPan({ x: dragRef.current.panX + dx, y: dragRef.current.panY + dy }); }; const onMouseUp = () => { dragRef.current = null; }; // viewBox stays fixed; we transform an inner group const viewW = 1000, viewH = 700; return (
{/* Land background */} {/* Wadi Hanifa — diagonal river-park strip */} {/* Tertiary grid */} {ROADS.tertiary.map((d, i) => )} {/* Secondary roads */} {ROADS.secondary.map((d, i) => )} {/* District subtle fills */} {showZoning && ( {/* Just a few large shapes for atmospheric color */} )} {/* Primary roads */} {ROADS.primary.map((r, i) => )} {/* Road name labels */} {showLabels && ( {ROADS.primary.map((r, i) => ( {r.name[lang]?.toUpperCase() || r.name.en.toUpperCase()} ))} )} {/* District labels */} {showLabels && ( {DISTRICT_LABELS.map(d => ( {districts[d.id]?.[lang] || districts[d.id]?.en} ))} )} {/* Parcels */} {filtered.map(p => { const isSelected = selectedId === p.id; const isHover = hover === p.id; const color = zoning[p.zone].color; return ( { e.stopPropagation(); onSelect(p.id); }} onMouseEnter={() => setHover(p.id)} onMouseLeave={() => setHover(h => h === p.id ? null : h)} /> {p.featured && !isSelected && ( )} ); })} {/* Selected parcel pin */} {(() => { const sel = filtered.find(p => p.id === selectedId); if (!sel) return null; const cx = sel.x + sel.w / 2; const cy = sel.y + sel.h / 2; return ( {sel.id} ); })()} {/* Compass rose */} N {/* Map controls */}
{/* Coordinate readout */}
24.7136° N · 46.6753° E · z{zoom.toFixed(2)}
); }; Object.assign(window, { RiyadhMap, BASEMAP_STYLE });