Skip to content

Commit 2e83000

Browse files
authored
Merge pull request #10 from SunbirdAI/development
Integration of postgis and postgresql
2 parents d10d4b6 + d5e38bb commit 2e83000

17 files changed

+4055
-316
lines changed

README.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,76 @@ Makefile # Dev workflow commands
3434
pip install -r requirements.txt
3535
```
3636

37-
### 2. Run Tests
37+
### 2. (Optional) Stand Up PostGIS via Docker
38+
39+
If you want to use the PostGIS-backed analyzer, bring up a local PostGIS container:
40+
41+
```sh
42+
docker run \
43+
--name suntrace-postgis \
44+
-e POSTGRES_DB=suntrace \
45+
-e POSTGRES_USER=pguser \
46+
-e POSTGRES_PASSWORD=pgpass \
47+
-p 5432:5432 \
48+
-d postgis/postgis:15-3.4
49+
```
50+
51+
Wait for the database to accept connections, then ensure the PostGIS extensions exist (the analyzer can do this for you, or run once inside the container):
52+
53+
```sh
54+
docker exec -it suntrace-postgis psql -U pguser -d suntrace -c "CREATE EXTENSION IF NOT EXISTS postgis;"
55+
docker exec -it suntrace-postgis psql -U pguser -d suntrace -c "CREATE EXTENSION IF NOT EXISTS postgis_topology;"
56+
```
57+
58+
### 3. Load Project Data into PostGIS
59+
60+
With the container running and data available under `data/`, load all vector layers:
61+
62+
```sh
63+
python scripts/load_to_postgis.py \
64+
--data-dir data \
65+
--db-uri postgresql://pguser:pgpass@localhost:5432/suntrace
66+
```
67+
68+
Requirements for the loader:
69+
70+
- `ogr2ogr` (GDAL) must be installed on the host running the script.
71+
```sh
72+
brew install gdal
73+
```
74+
- Python deps: `geopandas`, `pandas`, `sqlalchemy`.
75+
76+
> The script scans `data/`, `data/lamwo_sentinel_composites/`, `data/viz_geojsons/`, and `data/sample_region_mudu/`, writing tables such as `public.lamwo_buildings`, `public.lamwo_roads`, `public.lamwo_tile_stats_ee_biomass`, etc. Ensure filenames follow the repository defaults so table names match the analyzer’s expectations.
77+
78+
If you need the joined tile view, run inside a Python shell after the load:
79+
80+
```python
81+
from utils.GeospatialAnalyzer2 import GeospatialAnalyzer2
82+
analyzer = GeospatialAnalyzer2()
83+
analyzer.create_joined_tiles(
84+
tile_stats_table='public.lamwo_tile_stats_ee_biomass',
85+
plain_tiles_table='public.lamwo_grid'
86+
)
87+
```
88+
89+
### 4. Configure the App to Use PostGIS
90+
91+
Set the following in `.env` (already present in this repo by default):
92+
93+
```
94+
SUNTRACE_USE_POSTGIS=1
95+
SUNTRACE_DATABASE_URI=postgresql+psycopg://pguser:pgpass@localhost:5432/suntrace
96+
```
97+
98+
Restart the app after changing the env file. The factory will log which analyzer was initialized.
99+
100+
### 5. Run Tests
38101

39102
```sh
40103
make test
41104
```
42105

43-
### 3. Start the Application (Local)
106+
### 6. Start the Application (Local)
44107

45108
```sh
46109
uvicorn main:app --reload
@@ -76,7 +139,7 @@ docker logs -f suntracte
76139
docker-compose up -d --build
77140
```
78141

79-
### 5. Access Frontend
142+
### 7. Access Frontend
80143

81144
Open [http://localhost:8080](http://localhost:8080) in your browser.
82145

app/core/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from functools import lru_cache
77

88
from pydantic_settings import BaseSettings
9+
from typing import Optional
910

1011

1112
class Settings(BaseSettings):
@@ -50,6 +51,12 @@ class Settings(BaseSettings):
5051
# Building sample limit for performance
5152
BUILDING_SAMPLE_LIMIT: int = 2000
5253

54+
# Database settings
55+
SUNTRACE_USE_POSTGIS: bool = False
56+
SUNTRACE_DATABASE_URI: Optional[str] = None
57+
58+
59+
5360
class Config:
5461
env_file = ".env"
5562
case_sensitive = True

app/services/geospatial.py

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
Geospatial analysis service
33
"""
44

5-
import json
65
import time
76

87
from uuid import uuid4
@@ -62,7 +61,7 @@ def get_map_layers(self) -> MapLayersResponse:
6261
try:
6362
# Get bounds for the map from the plain tiles
6463
logger.info("Fetching map layers data.")
65-
bounds = self.analyzer._plain_tiles_gdf.total_bounds.tolist()
64+
bounds = self.analyzer.get_layer_bounds("tiles")
6665

6766
# Calculate center coordinates
6867
center = Coordinates(
@@ -78,26 +77,20 @@ def get_map_layers(self) -> MapLayersResponse:
7877
)
7978

8079
# Convert minigrids to GeoJSON
81-
candidate_minigrids_geo = json.loads(
82-
self.analyzer._candidate_minigrids_gdf.to_crs("EPSG:4326").to_json()
83-
)
80+
candidate_minigrids_geo = self.analyzer.get_layer_geojson("candidate_minigrids")
8481

8582
# Get a sample of buildings to avoid performance issues
86-
total_buildings = len(self.analyzer._buildings_gdf)
87-
building_sample = (
88-
self.analyzer._buildings_gdf.sample(
89-
min(settings.BUILDING_SAMPLE_LIMIT, total_buildings)
90-
)
91-
if total_buildings > settings.BUILDING_SAMPLE_LIMIT
92-
else self.analyzer._buildings_gdf
83+
total_buildings = self.analyzer.get_layer_count("buildings")
84+
buildings_geo = self.analyzer.get_layer_geojson(
85+
"buildings", sample=settings.BUILDING_SAMPLE_LIMIT
9386
)
94-
buildings_geo = json.loads(building_sample.to_crs("EPSG:4326").to_json())
87+
sampled_buildings = len(buildings_geo.get("features", []))
9588

9689
# Create metadata
9790
metadata = {
9891
"total_buildings": total_buildings,
99-
"sampled_buildings": len(building_sample),
100-
"total_minigrids": len(self.analyzer._candidate_minigrids_gdf),
92+
"sampled_buildings": sampled_buildings,
93+
"total_minigrids": self.analyzer.get_layer_count("candidate_minigrids"),
10194
"coordinate_system": self.analyzer.target_geographic_crs,
10295
}
10396
logger.info("Map layers data fetched successfully.")

configs/paths.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
# Data paths
88
DATA_DIR = PROJECT_ROOT / "data"
9-
TILE_STATS_PATH = DATA_DIR / "Lamwo_Tile_Stats_EE.csv"
9+
TILE_STATS_PATH = DATA_DIR / "Lamwo_Tile_Stats_EE_biomass.csv"
1010
PLAIN_TILES_PATH = DATA_DIR / "lamwo_sentinel_composites" / "lamwo_grid.geojson"
1111

1212
VIZUALIZATION_DIR = DATA_DIR / "viz_geojsons"

configs/stats.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"""Shared configuration for analyzer statistic columns."""
2+
3+
DEFAULT_TILE_STAT_COLUMNS = [
4+
"ndvi_mean",
5+
"ndvi_med",
6+
"ndvi_std",
7+
"evi_med",
8+
"elev_mean",
9+
"slope_mean",
10+
"par_mean",
11+
"rain_total_mm",
12+
"rain_mean_mm_day",
13+
"cloud_free_days",
14+
]

main.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
"""
2-
Main FastAPI application entry point
3-
"""
1+
"""Main FastAPI application entry point."""
42

3+
import logging
54
import sys
65
from pathlib import Path
6+
77
import uvicorn
8+
89
from app.api.deps import create_application
910
from app.core.config import get_settings
1011

@@ -15,6 +16,9 @@
1516
sys.path.insert(0, str(project_root))
1617

1718

19+
logging.basicConfig(level=logging.INFO)
20+
logging.getLogger("GeospatialAnalyzer2").setLevel(logging.INFO)
21+
1822
settings = get_settings()
1923
app = create_application()
2024

notebooks/Precomputed Aggregates.ipynb

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -825,15 +825,6 @@
825825
"stats_table.toList(10).getInfo()"
826826
]
827827
},
828-
{
829-
"cell_type": "code",
830-
"execution_count": null,
831-
"metadata": {
832-
"id": "D4Wk_r2_4Woi"
833-
},
834-
"outputs": [],
835-
"source": []
836-
},
837828
{
838829
"cell_type": "code",
839830
"execution_count": null,

0 commit comments

Comments
 (0)