@extends('portal.layout') @section('title', 'AI Site Plan') @section('content')
@include('portal.partials.report-sidebar', ['report' => $report])
AI Site Planning

Site Plan Generator

AI generates optimized 2D site plans with building footprints, parking, setbacks and open space allocation.

@if(session('success'))
{{ session('success') }}
@endif
@csrf
@if($plans->count()) {{-- Plan selector tabs --}}
@foreach($plans as $pi => $plan) @endforeach
@foreach($plans as $pi => $plan)
{{-- Header --}}
{{ $plan->title ?? 'Site Plan' }}
{{ ucfirst($plan->plan_type ?? 'auto') }} · Generated {{ $plan->created_at->format('M j, Y H:i') }}
@if($plan->score)
{{ $plan->score }}
Score
@endif
{{-- Metrics --}} @if($plan->metrics)
@php $metricConfig = [ 'coverage_pct' => ['label' => 'Coverage', 'suffix' => '%', 'format' => 'number'], 'open_space_pct' => ['label' => 'Open Space', 'suffix' => '%', 'format' => 'number'], 'total_builtup' => ['label' => 'Built-up', 'suffix' => ' sqm','format' => 'comma'], 'parking_count' => ['label' => 'Parking', 'suffix' => ' ECS','format' => 'int'], 'building_count' => ['label' => 'Buildings', 'suffix' => '', 'format' => 'int'], ]; @endphp @foreach($metricConfig as $mKey => $mConf) @if(isset($plan->metrics[$mKey]))
{{ $mConf['label'] }}
@if($mConf['format'] === 'comma') {{ number_format($plan->metrics[$mKey]) }}{{ $mConf['suffix'] }} @elseif($mConf['format'] === 'number') {{ number_format($plan->metrics[$mKey], 1) }}{{ $mConf['suffix'] }} @else {{ $plan->metrics[$mKey] }}{{ $mConf['suffix'] }} @endif
@endif @endforeach
@endif {{-- SVG Plan Visualization --}} @php // Compute bounding box from all polygon data to scale SVG properly $allCoords = []; foreach (($plan->building_footprints ?? []) as $fp) { foreach (($fp['polygon'] ?? []) as $pt) { $allCoords[] = $pt; } } foreach (($plan->parking_zones ?? []) as $pz) { foreach (($pz['polygon'] ?? []) as $pt) { $allCoords[] = $pt; } } foreach (($plan->landscape_areas ?? []) as $la) { foreach (($la['polygon'] ?? []) as $pt) { $allCoords[] = $pt; } } // If we have polygon data, compute bounds; otherwise use fixed dims if (!empty($allCoords)) { $xs = array_column($allCoords, 0); $ys = array_column($allCoords, 1); $rawMinX = min($xs); $rawMinY = min($ys); $rawMaxX = max($xs); $rawMaxY = max($ys); } else { $rawMinX = 0; $rawMinY = 0; $rawMaxX = 100; $rawMaxY = 100; } // Add padding around the plot $padFrac = 0.15; $rawW = max($rawMaxX - $rawMinX, 1); $rawH = max($rawMaxY - $rawMinY, 1); $pad = max($rawW, $rawH) * $padFrac; $vbX = $rawMinX - $pad; $vbY = $rawMinY - $pad; $vbW = $rawW + $pad * 2; $vbH = $rawH + $pad * 2; // Plot boundary rectangle (from 0,0 to plotW,plotD in service coords) // Estimate plot dimensions same as service $plotArea = $report->plot_area_sqm ?? 3317; $ratio = 0.6; $plotDepth = sqrt($plotArea / $ratio); $plotWidth = $plotArea / $plotDepth; $resultData = $report->result_data ?? []; $frontSetback = $resultData['front_setback_m'] ?? 6; $sideSetback = $resultData['side_setback_m'] ?? 3; $rearSetback = $resultData['rear_setback_m'] ?? 3; @endphp
{{-- Legend --}}
Building Parking Landscape Setback Line Plot Boundary
{{-- Grid lines --}} @for($gx = 0; $gx <= $plotWidth; $gx += 10) @endfor @for($gy = 0; $gy <= $plotDepth; $gy += 10) @endfor {{-- Plot boundary --}} {{-- Setback lines (dashed orange) --}} {{-- Road indicator --}} ROAD ({{ $report->road_width_m ?? 9 }}m) {{-- Landscape areas --}} @foreach(($plan->landscape_areas ?? []) as $la) @if(!empty($la['polygon'])) @php $pts = implode(' ', array_map(fn($p) => $p[0].','.$p[1], $la['polygon'])); @endphp @php $laCx = array_sum(array_column($la['polygon'], 0)) / count($la['polygon']); $laCy = array_sum(array_column($la['polygon'], 1)) / count($la['polygon']); @endphp {{ ucfirst($la['type'] ?? 'Garden') }} @endif @endforeach {{-- Parking zones --}} @foreach(($plan->parking_zones ?? []) as $pz) @if(!empty($pz['polygon'])) @php $pts = implode(' ', array_map(fn($p) => $p[0].','.$p[1], $pz['polygon'])); @endphp @php $pzCx = array_sum(array_column($pz['polygon'], 0)) / count($pz['polygon']); $pzCy = array_sum(array_column($pz['polygon'], 1)) / count($pz['polygon']); @endphp Parking {{ $pz['capacity'] ?? '' }} {{ ($pz['type'] ?? '') === 'stilt' ? '(Stilt)' : '' }} @endif @endforeach {{-- Building footprints --}} @php $bColors = ['#3b82f6', '#8b5cf6', '#06b6d4', '#22c55e', '#f59e0b']; @endphp @foreach(($plan->building_footprints ?? []) as $bi => $fp) @if(!empty($fp['polygon'])) @php $bColor = $bColors[$bi % count($bColors)]; $pts = implode(' ', array_map(fn($p) => $p[0].','.$p[1], $fp['polygon'])); $fpCx = array_sum(array_column($fp['polygon'], 0)) / count($fp['polygon']); $fpCy = array_sum(array_column($fp['polygon'], 1)) / count($fp['polygon']); $floors = $fp['floors'] ?? 1; @endphp {{ $fp['label'] ?? 'Block ' . ($bi + 1) }} {{ $floors }}F · {{ ucfirst($fp['use'] ?? 'residential') }} @endif @endforeach {{-- Access points --}} @foreach(($plan->access_points ?? []) as $ap) @php $apx = $ap['position'][0] ?? 0; $apy = $ap['position'][1] ?? 0; @endphp {{ ($ap['type'] ?? '') === 'main_entry' ? 'Entry' : (($ap['type'] ?? '') === 'emergency' ? 'Emergency' : 'Access') }} @endforeach {{-- Dimension labels --}} {{ number_format($plotWidth, 1) }}m {{ number_format($plotDepth, 1) }}m
{{-- AI Rationale --}} @if($plan->ai_rationale) @php $rationale = is_array($plan->ai_rationale) ? ($plan->ai_rationale['rationale'] ?? '') : $plan->ai_rationale; @endphp @if($rationale)
Design Rationale
{{ $rationale }}
@endif @endif {{-- Constraint Summary --}}
Applied Constraints
@php $rd = $report->result_data ?? []; @endphp @foreach([ 'Plot' => number_format($report->plot_area_sqm ?? 0) . ' sqm', 'FAR' => $rd['max_far'] ?? '—', 'Height' => ($rd['max_height_m'] ?? '—') . 'm', 'Coverage' => ($rd['coverage_pct'] ?? '—') . '%', 'Floors' => $rd['max_floors'] ?? '—', 'Front' => ($rd['front_setback_m'] ?? '—') . 'm', 'Side' => ($rd['side_setback_m'] ?? '—') . 'm', 'Rear' => ($rd['rear_setback_m'] ?? '—') . 'm', ] as $cl => $cv) {{ $cl }}: {{ $cv }} @endforeach
@endforeach @else
No Site Plans Yet
Choose a project type and generate optimized site plans.
@endif
@endsection @section('scripts') @endsection