Skip to content

Sharann-del/Landroid

Repository files navigation

Landroid

A Flutter + FastAPI application for AI-powered land health monitoring and parcel management. Built for the Birdscale Technology hackathon at VIT Chennai.


Table of Contents

  1. Project Overview
  2. Architecture
  3. Directory Structure
  4. Frontend — Flutter
  5. Backend — FastAPI
  6. Supabase Integration
  7. External APIs
  8. Running the App
  9. Dependencies

Project Overview

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).


Architecture

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.


Directory Structure

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

Frontend — Flutter

Initialization (main.dart)

On startup:

  1. WidgetsFlutterBinding.ensureInitialized()
  2. initSupabase() — connects to Supabase with PKCE auth flow
  3. StorageService.init() — opens Hive boxes (users, parcels, documents, alerts, settings)
  4. _seedDemoData() — loads demo parcels/alerts if Hive boxes are empty
  5. ProviderScope(LandroidApp) — starts Riverpod

Theme

Material 3. Primary color: forest green #2E7D32. Secondary: light green #4CAF50.

Navigation (GoRouter)

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.

Localization

English (app_en.arb) and Tamil (app_ta.arb). Delegates registered at app root.


Feature Modules

Auth (features/auth/)

Provider: AuthNotifier (StateNotifier)

State fields: isLoggedIn, isLoading, needsOnboarding, userId, role, user, error.

Phone OTP flow:

  1. sendPhoneOtp(phone) — sends OTP via Supabase Auth
  2. verifyPhoneOtpLogin(phone, otp) or verifyPhoneOtpRegister(phone, otp, name, role) — verifies token
  3. On success, _loadProfile(userId) fetches profiles row from Supabase

Google OAuth flow:

  1. signInWithGoogle() — triggers OAuth redirect
  2. onAuthStateChange listener catches the callback
  3. If no profile exists → needsOnboarding = true → user fills OnboardingPage
  4. completeOnboarding(name, role) — inserts row into profiles

Demo mode is supported (bypasses real auth).


Parcels (features/parcels/)

Model: ParcelModel

  • id, consultantId, name
  • boundary — 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 Supabase
  • ParcelRepositoryImpl — mock data for development

Key queries:

  • By consultant: parcels WHERE consultant_id = $id
  • By landowner: join parcel_assignmentsparcels WHERE landowner_id = $id

Screens: ParcelListScreen, CreateParcelScreen (with boundary drawing on map), ParcelDetailScreen.

Widgets: RasterUploader, BoundaryDrawingMap, HealthBadge, NdviChart, RainfallChart.


Land Health (features/ai_modules/land_health/)

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: initialloadingloaded / 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.


Plant Health Zone Map (features/ai_modules/plant_health_zone_map/)

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.


Tree Count (features/ai_modules/tree_count/)

Provider: TreeCountNotifier (AsyncNotifier)

Fetches from GET http://10.0.2.2:8000/tree-count.

Model: TreeCountModel

  • totalCount, stressedCount, healthyCount
  • densityPerAcre, areaAcres
  • confidence, 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).


Land Valuation (features/ai_modules/land_valuation/)

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.


Map (features/map/)

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.


Documents (features/documents/)

Model: DocumentModelid, 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.


Alerts (features/alerts/)

Model: AlertModelid, 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.


Other Features

  • 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

Backend — FastAPI

Entry point: backend/main.py
Server: uvicorn on port 8000
CORS: open (*) for Android emulator loopback at 10.0.2.2

NDVI Cache

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)

Endpoints

GET /health

{ "status": "ok" }

GET /boundary

Returns the contents of data/boundary.geojson as a GeoJSON FeatureCollection. Used by the Flutter map to draw the parcel boundary overlay.


GET /plant-health

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
}

GET /tree-count

Fast, lightweight tree estimation from NDVI zones. No OpenCV required.

Process:

  1. Count pixels in healthy (NDVI 0.4–0.6) + dense (NDVI > 0.6) zones
  2. treeCount = canopy_pixels / 50 (average canopy = 50 px at 1024 px resolution)
  3. stressedCount = round(treeCount * 0.12)
  4. healthyCount = treeCount - stressedCount
  5. densityPerAcre = 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": []
}

GET /ndvi_zones.png

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.


canopy_analysis.py (Standalone)

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:

  1. Read and downsample orthomosaic
  2. CLAHE contrast enhancement in LAB color space
  3. HSV vegetation mask (H: 15–95, S: 40–255, V: 40–255)
  4. Morphological open/close to clean the mask
  5. Distance transform + watershed segmentation
  6. Connected components → individual canopy regions
  7. Stress detection: radiusPx < 0.70 * median_radius
  8. Output JSON + overlay PNG

Supabase Integration

Project URL: https://fvbvbxtzsqvvrrsxecva.supabase.co
Auth: PKCE flow. Deep link callback: landroid://auth/callback.

Schema

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
)

Storage Buckets

Bucket Contents
rasters Orthomosaic, DEM, NDVI rasters (GeoTIFF/COG)
documents Land documents (PDF, PNG, JPG, TIFF)

External APIs

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

Running the App

Backend

cd backend
source venv/bin/activate
uvicorn main:app --reload --host 0.0.0.0 --port 8000

The server will be reachable at:

  • http://localhost:8000 from host machine
  • http://10.0.2.2:8000 from Android emulator

On first request, the orthomosaic is read, downsampled to 1024 px, and cached in memory. Subsequent requests are instant.

Flutter

flutter pub get
flutter run                  # default connected device
flutter run -d emulator-5554 # specific Android emulator
flutter run --release        # release build

Ensure the FastAPI backend is running before launching the app if you need the AI modules (plant health, tree count, NDVI overlay).

Standalone Canopy Analysis

cd backend
source venv/bin/activate
python canopy_analysis.py

Set CANOPY_ORTHO_PATH and CANOPY_OUTPUT_DIR environment variables or edit the paths at the top of the script.


Dependencies

Flutter (pubspec.yaml)

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

Backend (requirements.txt)

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

About

Landroid is a land management platform that 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)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors