A Flutter + FastAPI application for AI-powered land health monitoring and parcel management. Built for the Birdscale Technology hackathon at VIT Chennai.
- Project Overview
- Architecture
- Directory Structure
- Frontend — Flutter
- Backend — FastAPI
- Supabase Integration
- External APIs
- Running the App
- Dependencies
Landroid is a land management platform with two user roles:
- Consultant — creates and manages parcels, uploads drone imagery, views AI analysis, assigns parcels to landowners
- Landowner — read-only view of their assigned parcels, health data, alerts, and documents
The app uses drone-captured orthomosaic imagery (GeoTIFF) as the primary data source. A FastAPI backend processes this imagery to derive NDVI-based insights — plant health zone maps, tree count estimation, and overlay PNGs. All parcel data, user profiles, and documents are stored in Supabase (PostgreSQL + PostGIS).
Flutter App (Dart + Riverpod)
|
|-- Supabase (Auth, PostgreSQL/PostGIS, Storage)
|-- FastAPI Backend (localhost:8000 / 10.0.2.2:8000 on Android emulator)
| |-- orthomosaic.tif (NDVI, plant health, tree count, PNG overlay)
| |-- boundary.geojson (parcel boundary)
| |-- dem.tif (elevation raster)
|
|-- Planetary Computer (Sentinel-2 NDVI, CHIRPS rainfall, ERA5 temperature, VIIRS night lights)
|-- ISRIC SoilGrids (soil type, pH, carbon)
|-- OpenStreetMap Overpass + Nominatim (proximity scoring for valuation)
State management is Riverpod throughout. Navigation is GoRouter with role-based redirect logic. Local caching uses Hive.
Landroid_Flutter/
├── lib/
│ ├── main.dart # App entry point
│ ├── firebase_options.dart # Firebase config
│ ├── routes/
│ │ └── app_router.dart # GoRouter config, protected routes
│ ├── core/
│ │ ├── constants/app_constants.dart
│ │ ├── theme/app_theme.dart
│ │ └── widgets/ # Shared UI components
│ ├── l10n/ # English + Tamil ARB files
│ ├── shared/
│ │ ├── models/user_model.dart
│ │ ├── services/
│ │ │ ├── supabase_service.dart
│ │ │ ├── storage_service.dart
│ │ │ └── supabase_storage_service.dart
│ │ └── utils/
│ └── features/
│ ├── auth/
│ ├── parcels/
│ ├── ai_modules/
│ │ ├── land_health/
│ │ ├── plant_health_zone_map/
│ │ ├── tree_count/
│ │ └── land_valuation/
│ ├── consultant/
│ ├── landowner/
│ ├── map/
│ ├── documents/
│ ├── alerts/
│ ├── reports/
│ ├── messaging/
│ ├── scanning/
│ ├── settings/
│ └── profile/
├── backend/
│ ├── main.py # FastAPI server
│ ├── canopy_analysis.py # Standalone OpenCV canopy script
│ ├── requirements.txt
│ ├── data/
│ │ ├── orthomosaic.tif # 2.7 GB drone orthomosaic
│ │ ├── dem.tif # 377 MB elevation raster
│ │ └── boundary.geojson # Parcel boundary (Kallapuram, Tamil Nadu)
│ └── venv/
├── assets/
│ ├── images/
│ ├── icons/
│ ├── translations/
│ └── data/
├── pubspec.yaml
└── analysis_options.yaml
On startup:
WidgetsFlutterBinding.ensureInitialized()initSupabase()— connects to Supabase with PKCE auth flowStorageService.init()— opens Hive boxes (users, parcels, documents, alerts, settings)_seedDemoData()— loads demo parcels/alerts if Hive boxes are emptyProviderScope(LandroidApp)— starts Riverpod
Material 3. Primary color: forest green #2E7D32. Secondary: light green #4CAF50.
Routes are protected. AuthNotifier drives redirect logic:
- Unauthenticated →
/login - Authenticated, needs onboarding →
/onboarding - Consultant →
/consultant/home - Landowner →
/landowner/home
Shell route wraps the main app with a bottom navigation bar with six tabs: Home, Map, Scan, Reports, Messages, Profile.
English (app_en.arb) and Tamil (app_ta.arb). Delegates registered at app root.
Provider: AuthNotifier (StateNotifier)
State fields: isLoggedIn, isLoading, needsOnboarding, userId, role, user, error.
Phone OTP flow:
sendPhoneOtp(phone)— sends OTP via Supabase AuthverifyPhoneOtpLogin(phone, otp)orverifyPhoneOtpRegister(phone, otp, name, role)— verifies token- On success,
_loadProfile(userId)fetchesprofilesrow from Supabase
Google OAuth flow:
signInWithGoogle()— triggers OAuth redirectonAuthStateChangelistener catches the callback- If no profile exists →
needsOnboarding = true→ user fillsOnboardingPage completeOnboarding(name, role)— inserts row intoprofiles
Demo mode is supported (bypasses real auth).
Model: ParcelModel
id,consultantId,nameboundary— GeoJSON Polygon (stored as PostGIS geometry in Supabase)centroid—[lat, lng]bbox—[minLng, minLat, maxLng, maxLat]healthScore,healthLabel,ndviTrend,rainfallMonthly,healthBreakdown
Repositories:
ParcelRepositorySupabase— production, queries SupabaseParcelRepositoryImpl— mock data for development
Key queries:
- By consultant:
parcels WHERE consultant_id = $id - By landowner: join
parcel_assignments→parcels WHERE landowner_id = $id
Screens: ParcelListScreen, CreateParcelScreen (with boundary drawing on map), ParcelDetailScreen.
Widgets: RasterUploader, BoundaryDrawingMap, HealthBadge, NdviChart, RainfallChart.
Computes an overall health score (0–100) for a parcel from four signals.
Signals:
| Signal | Source |
|---|---|
| NDVI trend | Sentinel-2 via Planetary Computer |
| Rainfall | CHIRPS via Planetary Computer |
| Temperature | ERA5 via Planetary Computer |
| Soil quality | ISRIC SoilGrids (pH, carbon, soil type) |
Provider: LandHealthNotifier
States: initial → loading → loaded / error.
ComputeHealthScore use case aggregates signal values and weights them into a final score with a confidence percentage.
Screen: Shows a score gauge, individual signal cards with current value, trend arrow, and historical sparkline chart.
Provider: ZoneNotifier
Fetches NDVI zone percentages from FastAPI GET /plant-health and a PNG overlay from GET /ndvi_zones.png.
Zone classification:
| Zone | NDVI Range | Color |
|---|---|---|
| Bare/Stressed | < 0.2 | Red #DC3218 |
| Sparse | 0.2 – 0.4 | Orange #FF8C00 |
| Healthy | 0.4 – 0.6 | Light Green #90EE90 |
| Dense | > 0.6 | Dark Green #006400 |
Screen: Zone legend with percentage breakdown, NDVI overlay on map, change summary vs previous period.
Provider: TreeCountNotifier (AsyncNotifier)
Fetches from GET http://10.0.2.2:8000/tree-count.
Model: TreeCountModel
totalCount,stressedCount,healthyCountdensityPerAcre,areaAcresconfidence,canopyLocations[]
CanopyLocation has id, latitude, longitude, radiusPx, areaPx2, stressed.
Screen: Displays count statistics and a canopy overlay on the map (locations currently empty — fast estimation mode).
Stub implementation. Planned valuation factors:
- Night light intensity — VIIRS from Planetary Computer (economic activity proxy)
- Road proximity — OSM Overpass API (nearest highway distance)
- Water proximity — OSM Overpass API (nearest water body)
- Town proximity — OSM Nominatim (nearest settlement)
Screen: ValueRangeCard, FactorBreakdown widget.
flutter_map with MapLibre GL. Supports layers:
- Orthomosaic (drone imagery)
- Parcel boundary (from
boundary.geojson) - NDVI zone overlay (PNG from FastAPI)
- DEM/Elevation
Offline tile caching up to 500 MB.
Model: DocumentModel — id, parcelId, name, type, storage_path, share_expires_at.
Types: FMB Sketch, Patta, Encumbrance Certificate (EC), GIS Snapshot.
Storage: Supabase Storage bucket documents. Upload/download/share with expiring links.
Model: AlertModel — id, parcelId, type, title, description, timestamp, isRead.
Types: BoundaryBreach, HealthChange, Insight.
Boundary breach uses virtual geofencing from the parcel's GeoJSON boundary with a configurable buffer (0–50 m). AlertHistoryScreen shows 90-day history.
- Settings — cache clearing, language selection (English/Tamil)
- Scanning — camera-based document/raster scanning
- Reports — GIS snapshot report generation
- Messaging — landowner-consultant in-app messaging
- Profile — user profile management
Entry point: backend/main.py
Server: uvicorn on port 8000
CORS: open (*) for Android emulator loopback at 10.0.2.2
All raster endpoints share a single in-process cache keyed on the orthomosaic file's mtime. The cache holds the downsampled NDVI array, bounding box, and band info. On first request (or after the file changes) it reads the orthomosaic, downsamples to a maximum of 1024 px on the longer side, and computes NDVI.
Band detection:
- 4-band file (Parrot Sequoia order: Green/Red/RedEdge/NIR) → true NDVI = (NIR - Red) / (NIR + Red)
- 3-band RGB → proxy NDVI = (Green - Red) / (Green + Red)
{ "status": "ok" }Returns the contents of data/boundary.geojson as a GeoJSON FeatureCollection. Used by the Flutter map to draw the parcel boundary overlay.
Reads the cached NDVI array, classifies every valid pixel into one of the four zones, and returns percentages.
Confidence score (0–100):
- Based on the fraction of non-NaN pixels (up to 80 pts)
- Penalised by the fraction of NDVI values outside
[-0.5, 1.0](up to 30 pts deduction)
Response:
{
"zones": {
"bare_stressed": 12.3,
"sparse": 24.1,
"healthy": 41.5,
"dense": 22.1
},
"confidence": 87.4,
"bbox": { "west": 77.1, "south": 11.0, "east": 77.2, "north": 11.1 },
"band_info": "Multispectral (4-band) detected ...",
"valid_pixel_ratio": 94.2
}Fast, lightweight tree estimation from NDVI zones. No OpenCV required.
Process:
- Count pixels in healthy (NDVI 0.4–0.6) + dense (NDVI > 0.6) zones
treeCount = canopy_pixels / 50(average canopy = 50 px at 1024 px resolution)stressedCount = round(treeCount * 0.12)healthyCount = treeCount - stressedCountdensityPerAcre = treeCount / 12.4
Response:
{
"treeCount": 184,
"stressedCount": 22,
"healthyCount": 162,
"densityPerAcre": 14.8,
"areaAcres": 12.4,
"confidenceScore": 72.0,
"confidence_note": "Estimated from NDVI zone analysis - not OpenCV watershed",
"canopyLocations": []
}Returns an RGBA PNG of the NDVI classification at the downsampled resolution (max 1024 px). Pixels outside the parcel data are transparent. Used as a map overlay in Flutter.
Full OpenCV watershed-based canopy detection. Not used by the FastAPI server (too slow for the 22027x27363 px orthomosaic at full resolution). Run independently if needed.
Process:
- Read and downsample orthomosaic
- CLAHE contrast enhancement in LAB color space
- HSV vegetation mask (H: 15–95, S: 40–255, V: 40–255)
- Morphological open/close to clean the mask
- Distance transform + watershed segmentation
- Connected components → individual canopy regions
- Stress detection:
radiusPx < 0.70 * median_radius - Output JSON + overlay PNG
Project URL: https://fvbvbxtzsqvvrrsxecva.supabase.co
Auth: PKCE flow. Deep link callback: landroid://auth/callback.
profiles (
id UUID PRIMARY KEY -- references auth.users(id)
full_name TEXT,
role TEXT, -- 'consultant' or 'landowner'
phone TEXT
)
parcels (
id UUID PRIMARY KEY,
consultant_id UUID REFERENCES profiles(id),
name TEXT,
boundary geometry(Polygon, 4326), -- PostGIS
centroid geometry(Point, 4326),
bbox FLOAT8[4],
health_score FLOAT4,
health_label TEXT,
ndvi_trend JSONB,
rainfall_monthly JSONB,
health_breakdown JSONB,
created_at TIMESTAMPTZ
)
parcel_assignments (
parcel_id UUID REFERENCES parcels(id),
landowner_id UUID REFERENCES profiles(id),
PRIMARY KEY (parcel_id, landowner_id)
)
documents (
id UUID PRIMARY KEY,
parcel_id UUID REFERENCES parcels(id),
name TEXT,
type TEXT, -- 'fmb_sketch', 'patta', 'ec', 'gis_snapshot'
storage_path TEXT,
share_expires_at TIMESTAMPTZ,
created_at TIMESTAMPTZ
)| Bucket | Contents |
|---|---|
rasters |
Orthomosaic, DEM, NDVI rasters (GeoTIFF/COG) |
documents |
Land documents (PDF, PNG, JPG, TIFF) |
| API | Purpose | Endpoint |
|---|---|---|
| Planetary Computer STAC | Sentinel-2 NDVI time series | planetarycomputer.microsoft.com |
| Planetary Computer STAC | CHIRPS rainfall | planetarycomputer.microsoft.com |
| Planetary Computer STAC | ERA5 temperature | planetarycomputer.microsoft.com |
| Planetary Computer STAC | VIIRS night lights | planetarycomputer.microsoft.com |
| ISRIC SoilGrids | Soil type, pH, organic carbon | rest.isric.org |
| OSM Overpass | Road and water body proximity | overpass-api.de |
| OSM Nominatim | Town/settlement proximity | nominatim.openstreetmap.org |
cd backend
source venv/bin/activate
uvicorn main:app --reload --host 0.0.0.0 --port 8000The server will be reachable at:
http://localhost:8000from host machinehttp://10.0.2.2:8000from Android emulator
On first request, the orthomosaic is read, downsampled to 1024 px, and cached in memory. Subsequent requests are instant.
flutter pub get
flutter run # default connected device
flutter run -d emulator-5554 # specific Android emulator
flutter run --release # release buildEnsure the FastAPI backend is running before launching the app if you need the AI modules (plant health, tree count, NDVI overlay).
cd backend
source venv/bin/activate
python canopy_analysis.pySet CANOPY_ORTHO_PATH and CANOPY_OUTPUT_DIR environment variables or edit the paths at the top of the script.
| Package | Purpose |
|---|---|
flutter_riverpod ^2.4.9 |
State management |
go_router ^13.0.0 |
Navigation and deep linking |
supabase_flutter ^2.5.6 |
Supabase client (auth + database + storage) |
flutter_map ^6.1.0 |
Map rendering (MapLibre GL) |
latlong2 ^0.9.0 |
Coordinate types |
hive ^2.2.3 + hive_flutter |
Local storage |
http ^1.2.1 |
HTTP client |
dio ^5.4.0 |
Advanced HTTP client |
fl_chart ^0.66.0 |
NDVI and rainfall charts |
file_picker ^8.0.0 |
File selection for uploads |
flutter_secure_storage ^9.2.2 |
Secure token storage |
app_links ^3.4.5 |
Deep link handling (OAuth callback) |
equatable ^2.0.5 |
Value equality |
uuid ^4.3.3 |
UUID generation |
intl ^0.20.2 |
Internationalization |
| Package | Purpose |
|---|---|
fastapi |
Web framework |
uvicorn |
ASGI server |
rasterio |
GeoTIFF read with downsampling |
numpy |
Array operations (NDVI math, zone classification) |
opencv-python |
Canopy detection (standalone script only) |
scipy |
Scientific computing |
shapely |
Geometric operations |
matplotlib |
Visualization (standalone script) |
python-multipart |
Multipart form data |