Eishaan's picture
Add KML terrain and soil analysis layers
2f91d7e
Raw
History Blame Contribute Delete
26.9 kB
from __future__ import annotations
from .evidence import evidence_to_markdown_table
from .models import ReportBundle, SiteSelection
from .safety import assert_safe_text, safety_block
def build_board_bundle(
project_name: str,
site_name: str,
selection: SiteSelection,
climate: dict,
osm_context: dict,
sun_summary: dict[str, str],
site_identity: dict | None,
evidence_rows,
diagram_paths: list[str],
topography: dict | None = None,
soil: dict | None = None,
warnings: list[str] | None = None,
) -> ReportBundle:
board = build_board_markdown(
project_name,
site_name,
selection,
climate,
osm_context,
sun_summary,
site_identity,
topography=topography,
soil=soil,
)
checklist = build_site_visit_checklist()
bundle = ReportBundle(
board_markdown=board,
evidence_rows=list(evidence_rows),
checklist_markdown=checklist,
diagram_paths=diagram_paths,
export_path=None,
warnings=warnings or [],
)
return bundle
def build_board_markdown(
project_name: str,
site_name: str,
selection: SiteSelection,
climate: dict,
osm_context: dict,
sun_summary: dict[str, str],
site_identity: dict | None = None,
topography: dict | None = None,
soil: dict | None = None,
) -> str:
area = _fmt(selection.area_sqm, "sqm")
perimeter = _fmt(selection.perimeter_m, "m")
context_counts = osm_context.get("counts") or {}
context_text = ", ".join(f"{k}: {v}" for k, v in list(context_counts.items())[:6]) or "OSM context unavailable or sparse."
climate_text = _climate_summary_lines(climate)
return f"""## Board Preview
**Project:** {project_name or "Untitled project"}
**Site:** {site_name or "Untitled site"}
**Input mode:** {selection.selection_type}
**Accuracy label:** {selection.accuracy_label}
### Site Identity
{_site_identity_text(site_identity, selection)}
### Boundary Summary
- Area: {area}
- Perimeter: {perimeter}
- Anchor / centroid: {_centroid_text(selection)}
- Source / unit: {selection.unit_source}
- Limitations: {"; ".join(selection.limitations)}
### Climate Captions
{climate_text}
### Geographic Context
{context_text}
### Terrain / Soil Signals
{_topography_summary(topography)}
{_soil_summary(soil)}
### Sun / Wind
- {sun_summary.get("orientation_note", "Orientation summary unavailable.")}
- {sun_summary.get("east_west_note", "")}
- {sun_summary.get("wind_note", "")}
- Limitation: {sun_summary.get("limitation", "No shadow simulation.")}
### Constraints / Opportunities
- Treat west and afternoon exposure as a likely shading/glare item to verify.
- Treat mapped roads/access as preliminary until road width and entry conditions are checked.
- Treat water, green, built-up, and amenity context as source-backed only where evidence rows exist.
- Keep culture/demographic context as editable field observations unless supported by reliable sources.
{build_detailed_site_analysis(selection, climate, osm_context, sun_summary, site_identity, topography, soil)}
"""
def build_detailed_site_analysis(
selection: SiteSelection,
climate: dict,
osm_context: dict,
sun_summary: dict[str, str],
site_identity: dict | None,
topography: dict | None = None,
soil: dict | None = None,
) -> str:
context_counts = osm_context.get("counts") or {}
current = climate.get("forecast") or {}
recent = climate.get("recent_historical") or {}
normal = climate.get("climate_normal") or {}
return f"""
## Detailed Site Analysis Workbook
This section is intentionally longer than the presentation board. It is meant to help a student understand what to draw, what to write, what is already supported by data, and what must still be checked on site.
### Reference Framework
The report structure uses common architecture and urban-design site-analysis categories, rewritten into this app's own evidence-first format:
- Urban Design Lab's site-analysis guide groups analysis around site location, neighbourhood context, site-specific conditions, natural features, man-made features, circulation, utilities, sensory conditions, human/cultural context, and climate. Source: https://urbandesignlab.in/urban-design-site-analysis/
- First In Architecture separates hard data and soft data and treats site analysis as a way to understand external conditions before design decisions. Source: https://www.firstinarchitecture.co.uk/architecture-site-analysis-guide/
- Archisoup emphasizes site visits, visual aids, GIS/CAD/environmental tools, and checklists for turning site information into design proposals. Source: https://www.archisoup.com/architecture-site-analysis-introduction
- ProjectManager describes site analysis as combining social, historical, climatic, geographic, legal, and infrastructure aspects into maps, diagrams, and written analysis. Source: https://www.projectmanager.com/blog/site-analysis-in-architecture
These references are used as learning/checklist references only. They are not treated as factual evidence about this specific site.
### 1. Location And Administrative Context
**What the app found**
{_site_identity_text(site_identity, selection)}
**Why it matters**
- Establishes the site in relation to city, region, access routes, climate zone, and surrounding settlement pattern.
- Helps label studio sheets correctly and prevents analysis from becoming generic.
- Supports later research into local regulations, heritage, materials, and culture.
**What to draw or include**
- Location map at regional scale.
- City/town context map.
- Immediate access map with road names and approach directions.
- A short location note with latitude/longitude and approximate address.
**Verify manually**
- Final project address, plot name, survey number, ownership source, and administrative boundary.
- Whether the site is part of a heritage, tourism, coastal, institutional, or special planning zone.
### 2. Boundary, Size, Scale And Input Reliability
**What the app found**
- Input mode: {selection.selection_type}
- Accuracy label: {selection.accuracy_label}
- Area: {_fmt(selection.area_sqm, "sqm")}
- Perimeter: {_fmt(selection.perimeter_m, "m")}
- Anchor / centroid: {_centroid_text(selection)}
- Unit/source note: {selection.unit_source}
- Known limits: {"; ".join(selection.limitations)}
**Why it matters**
- Area and perimeter affect massing, open-space ratios, circulation length, service access, and phasing.
- Boundary reliability controls how strongly any plot-level conclusion can be trusted.
**What to draw or include**
- Boundary diagram with north arrow and scale.
- Area/perimeter box.
- Source label: faculty CAD, survey, KML/Google Earth, GeoJSON, drawn map, or pin-radius.
- Accuracy note beside every map-based conclusion.
**Verify manually**
- Compare the drawn/uploaded boundary with CAD, KML/GeoJSON, faculty drawing, or survey documents.
- Confirm whether any setbacks, easements, rights of way, coastal buffers, or restricted edges apply.
### 3. Neighbourhood Context
**What the app found**
{_context_count_lines(context_counts)}
**Why it matters**
- Surrounding land use, building heights, road hierarchy, public transport, activity nodes, and edges shape entry, privacy, visibility, noise, and frontage strategy.
- Neighbourhood context also tells whether the project should blend, contrast, repair, or intensify existing patterns.
**What to draw or include**
- Figure-ground / built-void diagram.
- Land-use or surrounding-use diagram.
- Nearby landmarks and activity nodes.
- Edge-condition diagram for each side of the site.
- Positive and negative context arrows.
**Verify manually**
- Building uses, building heights, active frontages, parking behavior, pedestrian movement, informal activity, and public transport stops.
- Any upcoming road widening, development, or local planning change.
### 4. Natural Features, Landscape And Drainage
**What the app can support now**
- The current prototype records mapped green/water features where OSM data is available.
{_topography_summary(topography)}
- It does not yet run contour extraction, flood-risk, NDVI, or tree-canopy analysis.
**Why it matters**
- Topography affects cut-fill, access gradients, views, drainage, retaining structures, and universal access.
- Vegetation affects shade, microclimate, ecology, site identity, and construction constraints.
- Water edges and drainage patterns affect risk, orientation, humidity, views, and setbacks.
**What to draw or include**
- Slope/contour diagram where contour or DEM data is available.
- Drainage direction and low-point diagram.
- Existing tree/vegetation map.
- Water-body and buffer/edge diagram where relevant.
- Landscape opportunities and constraints.
**Verify manually**
- Actual slope, waterlogging, erosion, soil dampness, tree species/health, drainage outlets, and seasonal water changes.
### 5. Man-Made Features, Built Context And Heritage
**What the app can support now**
- DXF layer scan can flag likely existing building, road, contour, water, vegetation, and built-up layers when layer names exist.
- OSM context can count nearby mapped roads, amenities, land use, green/open space, and water features.
**Why it matters**
- Existing structures, heritage elements, damaged buildings, retaining walls, fences, utilities, and previous land use can become design anchors or constraints.
- Heritage and adaptive reuse conditions are not just visual issues; they affect circulation, phasing, approval, and public meaning.
**What to draw or include**
- Existing built-form map.
- Heritage/significant-structure note.
- Material and architectural-character board.
- Demolition/retain/reuse diagram if existing structures are part of the project.
**Verify manually**
- Building condition, ownership, heritage status, previous use, contamination risk, structural damage, and what must be retained.
### 6. Movement, Access And Circulation
**What the app found**
{_movement_summary(context_counts)}
**Why it matters**
- Access governs arrival sequence, service movement, emergency entry, pedestrian safety, parking, public transport connection, and construction logistics.
- For resorts, campuses, institutions, housing, and public buildings, movement hierarchy often becomes the organizing diagram.
**What to draw or include**
- Vehicular approach diagram.
- Pedestrian approach diagram.
- Public transport / bus-stop / station proximity if relevant.
- Service and emergency access assumptions.
- Desire lines and conflict points.
**Verify manually**
- Road width, turning radius, traffic speed, peak hours, pedestrian crossings, bus stops, parking pressure, blocked entries, and construction access.
### 7. Utilities, Services And Infrastructure
**What the app can support now**
- The prototype does not claim utility locations from public data.
- Utilities must remain a site-visit and document-verification section unless user/CAD evidence is uploaded.
**Why it matters**
- Water, drainage, sewer, electricity, fire access, telecom, transformers, poles, and service corridors influence site planning and feasibility.
**What to draw or include**
- Existing utilities map if provided by CAD/site survey.
- Drainage outlet and service approach diagram.
- Missing-services checklist.
**Verify manually**
- Electricity poles, substations, overhead lines, water supply, sewer/stormwater drains, manholes, fire access, service roads, and any underground services.
### 8. Sensory Analysis: Views, Noise, Odour, Pollution And Experience
**What the app can support now**
- The app can prompt this analysis but cannot verify sensory conditions remotely.
**Why it matters**
- Views shape orientation, openings, public/private zoning, and experience.
- Noise, smell, glare, dust, traffic, and pollution can define buffers, planting, services, and facade strategy.
**What to draw or include**
- Positive views.
- Negative views.
- Noise source diagram.
- Smell/dust/pollution edge diagram.
- Arrival-sequence photos.
**Verify manually**
- Morning/evening light, glare, sea/road/industry smells, traffic noise, crowding, informal use, and seasonal changes.
### 9. Human, Cultural And Activity Context
**What the app can support now**
- User-written local/culture notes are included as user-provided evidence.
- The app does not invent demographics, culture, crime, income, or social behavior.
**Why it matters**
- Architecture is used by people with routines, rituals, economies, seasonal patterns, and expectations.
- For Indian sites, local climate, material culture, festival use, informal vendors, tourism, community memory, and daily movement can be decisive.
**What to draw or include**
- Activity mapping by time of day.
- User group matrix.
- Cultural/material reference board.
- Local typology and streetscape study.
- Stakeholder and conflict/opportunity notes.
**Verify manually**
- Local interviews, observation at different times, student/faculty/client brief, census/government sources if demographics are needed, and local planning documents.
### 10. Climate, Sun, Wind And Microclimate
**What the app found**
{_climate_summary_lines(climate)}
- Sun/orientation note: {sun_summary.get("orientation_note", "not available")}
- Wind note: {sun_summary.get("wind_note", "not available")}
**Why it matters**
- Climate affects shade, roof design, ventilation, material durability, outdoor comfort, drainage, landscape, and energy strategy.
- Sun path and western exposure help decide massing, openings, shading, courtyards, and thermal buffers.
- Wind direction is useful for ventilation concepts but needs local verification because public data is regional/modelled.
**What to draw or include**
- Sun-path / orientation diagram.
- Wind direction or wind-rose diagram.
- Monthly temperature-rainfall chart.
- Shading and heat-gain notes for east, west, north, and south edges.
- Outdoor comfort and rain-protection implications.
**Verify manually**
- Actual shade from adjacent buildings/trees, local wind channels, glare, humid pockets, drainage behavior after rain, and thermal comfort during site visit.
### 11. Soil, Ground And Foundation Caution
**What the app says safely**
{_soil_summary(soil)}
- This prototype does not generate final soil or foundation recommendations.
- Soil/foundation is treated as a professional-verification item.
**Why it matters**
- Soil texture, bearing capacity, water table, settlement, fill, erosion, and contamination can affect foundation design, excavation, retaining walls, drainage, and cost.
**What to draw or include**
- Soil information source note if available.
- Ground-risk checklist.
- Areas needing geotechnical confirmation.
**Verify manually**
- Geotechnical report, borehole data, soil test, local engineer input, water table, previous fill, nearby construction behavior, and drainage/settlement signs.
### 12. Regulation, Ownership And Legal Constraints
**What the app can support now**
- The app does not verify legal/cadastral boundaries, zoning, ownership, FSI/FAR, CRZ, heritage controls, setbacks, or approvals.
**Why it matters**
- Regulations define what can be built, where it can be built, how high it can be, how much open space is required, and what must be protected.
**What to draw or include**
- Regulation checklist.
- Setback/easement/right-of-way diagram if documents are available.
- Risk register for unknown approvals.
**Verify manually**
- Local development control regulations, coastal/heritage/environmental rules, ownership documents, survey/cadastral records, and authority approvals.
### 13. Issues, Constraints And Opportunities Matrix
| Theme | Likely issue | Opportunity | Evidence status | Action |
|---|---|---|---|---|
| Boundary | Boundary may be approximate depending on source | Use CAD/GeoJSON/survey to improve precision | User input / computed | Verify source and redraw if needed |
| Climate | Heat, rain, humidity, and wind are modelled/contextual | Use climate-responsive shading, ventilation, drainage | Public data / modelled | Confirm comfort and rain behavior on site |
| Access | Road/access data may be incomplete | Organize entry, service, and pedestrian hierarchy | OSM / field needed | Measure road widths and observe peak movement |
| Natural systems | Slope, trees, drainage, and water behavior need field evidence | Use landscape, shade, water edge, and drainage as design generators | Mostly field needed | Photograph and map on site |
| Built context | Surrounding heights, materials, and uses need observation | Build context-responsive massing and edge conditions | OSM/CAD/user notes | Survey edges and nearby typologies |
| Culture/activity | Social use cannot be inferred safely | Create locally grounded program and public/private transitions | User/site visit | Observe, interview, and document |
| Soil/ground | Ground conditions are not confirmed | Plan professional tests early | Professional verification | Request geotechnical input |
### 14. Diagram And Sheet Production Checklist
Use this as a board-making checklist:
- Regional location map.
- City/town context map.
- Site boundary and dimensions.
- Figure-ground / built-void.
- Land use / surrounding-use map.
- Road hierarchy and access.
- Pedestrian movement / desire lines.
- Public transport proximity.
- Sun path and shade exposure.
- Wind direction / ventilation cue.
- Temperature-rainfall-humidity chart.
- Slope/topography/drainage.
- Vegetation/tree cover.
- Water body / drainage edge.
- Views: positive and negative.
- Noise/odour/pollution sources.
- Heritage/existing structures.
- Materials, vernacular, typology.
- Utilities/services.
- Constraints-opportunities synthesis.
- Site visit photo key plan.
### 15. Before, During And After Site Visit
**Before site visit**
- Print/export boundary, access map, and checklist.
- Mark missing data: soil, utilities, road width, slope, tree condition, surrounding heights, noise, views, and local activity.
- Prepare photo list: north, south, east, west, corners, road edge, water/vegetation, existing structures, utilities, and approach sequence.
**During site visit**
- Walk the boundary and record edge conditions.
- Photograph all approach roads and entrances.
- Record shade, wind, noise, smell, dust, traffic, slope, drainage, waterlogging, trees, and local activity.
- Ask locals/security/faculty/client about seasonal flooding, traffic peaks, safety, ownership, and previous use.
**After site visit**
- Separate confirmed observations from assumptions.
- Update diagrams with photos and manual notes.
- Mark each report claim as public data, computed, user-observed, or professional-verification required.
- Convert constraints and opportunities into design moves.
"""
def _context_count_lines(context_counts: dict) -> str:
if not context_counts:
return "- OSM context unavailable or sparse for this selected radius."
lines = []
for key, value in list(context_counts.items())[:10]:
label = str(key).replace("_", " ")
try:
count = int(value)
except (TypeError, ValueError):
count = value
lines.append(f"- {label}: {count} mapped feature(s)")
return "\n".join(lines)
def _topography_summary(topography: dict | None) -> str:
if not topography:
return "- Topography/elevation unavailable in this run; verify slope, contours, drainage, and low points manually."
mean_elev = topography.get("mean_elevation_m", "n/a")
relief = topography.get("relief_m", "n/a")
slope = topography.get("approx_slope_pct", "n/a")
interpretation = topography.get("interpretation", "Use only as preliminary terrain context.")
return (
f"- Public elevation signal: mean {mean_elev} m, sampled relief {relief} m, "
f"approximate sampled slope {slope}%. {interpretation}"
)
def _soil_summary(soil: dict | None) -> str:
if not soil:
return "- Soil signal unavailable in this run; use geotechnical report, local engineer input, or official soil maps."
pieces = [soil.get("texture_signal", "soil texture signal unavailable")]
if soil.get("clay_pct") is not None:
pieces.append(f"clay {soil['clay_pct']}%")
if soil.get("sand_pct") is not None:
pieces.append(f"sand {soil['sand_pct']}%")
if soil.get("silt_pct") is not None:
pieces.append(f"silt {soil['silt_pct']}%")
if soil.get("ph_h2o") is not None:
pieces.append(f"pH {soil['ph_h2o']}")
implication = soil.get("design_implication", "Use only as a preliminary prompt for soil verification.")
return "- Preliminary SoilGrids signal: " + ", ".join(pieces) + f". {implication}"
def _climate_summary_lines(climate: dict) -> str:
forecast = climate.get("forecast") or {}
recent = climate.get("recent_historical") or {}
normal = climate.get("climate_normal") or {}
lines = []
if _has_values(forecast):
lines.append(
"- **Forecast/current:** "
f"temperature {forecast.get('current_temperature_c', 'n/a')} C, "
f"humidity {forecast.get('current_humidity_pct', 'n/a')}%, "
f"wind {forecast.get('current_wind_speed_kmh', 'n/a')} km/h. "
"Use for site-visit timing only."
)
else:
lines.append(
"- **Forecast/current:** unavailable in this run. Do not make immediate weather or site-visit-timing claims from this layer."
)
if _has_values(recent):
period = recent.get("period", "recent archive period")
lines.append(
"- **Recent historical:** retrieved from Open-Meteo historical archive "
f"for {period}; use only as modelled recent context."
)
else:
lines.append(
"- **Recent historical:** unavailable in this run. Verify recent heat, rainfall, humidity, and wind tendencies separately."
)
if _has_values(normal):
period = normal.get("period", "multi-year historical archive")
total_rain = normal.get("total_precipitation_mm", "n/a")
lines.append(
"- **Climate-normal style:** computed from Open-Meteo historical archive "
f"for {period}; approximate annual precipitation {total_rain} mm. "
"This is not an official climatological normal."
)
else:
lines.append(
"- **Climate-normal style:** unavailable in this run. Use a studio-approved climate source before making long-term climate claims."
)
return "\n".join(lines)
def _has_values(value: object) -> bool:
if not isinstance(value, dict):
return False
for item in value.values():
if isinstance(item, list):
if any(_has_values(child) if isinstance(child, dict) else child is not None for child in item):
return True
elif item is not None:
return True
return False
def _movement_summary(context_counts: dict) -> str:
if not context_counts:
return "- OSM road/access data was unavailable or sparse for this selected radius."
matches = []
for key, value in context_counts.items():
label = str(key).replace("_", " ")
lower = label.lower()
if any(token in lower for token in ["road", "access", "highway", "transport"]):
matches.append(f"{label}: {value}")
if matches:
return "- Road/access mapped features: " + ", ".join(matches[:5]) + "."
return "- No road/access-specific count was found in the retrieved OSM summary; verify approach roads manually."
def build_site_visit_checklist() -> str:
return """- Confirm actual plot edges and boundary source.
- Measure or verify access road width and entry points.
- Photograph road edge, pedestrian access, and service approach.
- Check drainage, low points, waterlogging, and runoff direction.
- Observe slope/topography and compare with any contour or DEM evidence.
- Record major trees, shade, vegetation health, and removal constraints.
- Check surrounding building heights and likely shadow/glare effects.
- Note noise, dust, traffic, smell, and activity peaks.
- Identify visible utilities/services and missing service information.
- Record local activity, community use, culture, materials, and typology.
- Ask for soil/geotechnical reports before any foundation decision.
- Take photos in all cardinal directions plus key edges/corners."""
def build_markdown_report(
project_name: str,
site_name: str,
selection: SiteSelection,
bundle: ReportBundle,
) -> str:
warnings = "\n".join(f"- {w}" for w in bundle.warnings)
report = f"""# Site Intelligence Studio Report
## Safety And Scope
{safety_block()}
## Site
- Project: {project_name or "Untitled project"}
- Site: {site_name or "Untitled site"}
- Input mode: {selection.selection_type}
- Accuracy: {selection.accuracy_label}
{bundle.board_markdown}
## Site Visit Checklist
{bundle.checklist_markdown}
## Evidence Table
{evidence_to_markdown_table(bundle.evidence_rows)}
## Warnings
{warnings if warnings else "- No additional warnings."}
"""
assert_safe_text(report)
return report
def _fmt(value: float | None, suffix: str) -> str:
if value is None:
return "not available"
return f"{value:,.1f} {suffix}"
def _centroid_text(selection: SiteSelection) -> str:
if selection.anchor_lat is not None and selection.anchor_lon is not None:
return f"{selection.anchor_lat:.6f}, {selection.anchor_lon:.6f}"
if selection.centroid:
return f"{selection.centroid[0]:.2f}, {selection.centroid[1]:.2f}"
return "not available"
def _site_identity_text(site_identity: dict | None, selection: SiteSelection) -> str:
lines = [
f"- Latitude / longitude: {_centroid_text(selection)}",
]
if not site_identity:
lines.append("- Address context: not available.")
return "\n".join(lines)
ordered = [
("Approximate address", site_identity.get("display_name")),
("Street / road", site_identity.get("road")),
("Neighbourhood", site_identity.get("neighbourhood")),
("City / town", site_identity.get("city")),
("District", site_identity.get("district")),
("State", site_identity.get("state")),
("Country", site_identity.get("country")),
("Postcode", site_identity.get("postcode")),
]
for label, value in ordered:
if value:
lines.append(f"- {label}: {value}")
if len(lines) == 1:
lines.append("- Address context: reverse geocoding returned no usable address fields.")
lines.append("- Address note: OSM-derived address context is approximate and must be verified.")
return "\n".join(lines)