Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create APIs for address risk #131

Merged
merged 9 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,7 @@ node_modules
# Sensitive config
.env.development.local
.env
.env.local
.env.local

# IDEs
.vscode/
3 changes: 1 addition & 2 deletions backend/api/models/landslide_zones.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""All data of the Landslide Zones table from SFData"""

from sqlalchemy import String, Integer, DateTime, func, Float
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy import Integer, DateTime, func, Float
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from geoalchemy2 import Geometry
Expand Down
1 change: 0 additions & 1 deletion backend/api/models/tsunami.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"""Tsunami Risk Zone data"""

from sqlalchemy import String, Integer, Float, DateTime, func
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column
from geoalchemy2 import Geometry
Expand Down
24 changes: 24 additions & 0 deletions backend/api/routers/liquefaction_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import Depends, HTTPException, APIRouter
from ..tags import Tags
from sqlalchemy.orm import Session
from geoalchemy2 import functions as geo_func
from backend.database.session import get_db
from ..schemas.liquefaction_schemas import (
LiquefactionFeature,
Expand Down Expand Up @@ -41,3 +42,26 @@ async def get_liquefaction_zones(db: Session = Depends(get_db)):
LiquefactionFeature.from_sqlalchemy_model(zone) for zone in liquefaction_zones
]
return LiquefactionFeatureCollection(type="FeatureCollection", features=features)


@router.get("/is-in-liquefaction-zone", response_model=bool)
async def is_in_liquefaction_zone(
lon: float, lat: float, db: Session = Depends(get_db)
):
"""
Check if a point is in a liquefaction zone.

Args:
lon (float): Longitude of the point.
lat (float): Latitude of the point.
db (Session): The database session dependency.

Returns:
bool: True if the point is in a liquefaction zone, False otherwise.
"""
query = db.query(LiquefactionZone).filter(
LiquefactionZone.geometry.ST_Contains(
geo_func.ST_SetSRID(geo_func.ST_GeomFromText(f"POINT({lon} {lat})"), 4326)
)
)
return db.query(query.exists()).scalar()
22 changes: 22 additions & 0 deletions backend/api/routers/seismic_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import Depends, HTTPException, APIRouter
from ..tags import Tags
from sqlalchemy.orm import Session
from geoalchemy2 import functions as geo_func
from backend.database.session import get_db
from ..schemas.seismic_schemas import (
SeismicFeature,
Expand Down Expand Up @@ -39,3 +40,24 @@ async def get_seismic_hazard_zones(db: Session = Depends(get_db)):

features = [SeismicFeature.from_sqlalchemy_model(zone) for zone in seismic_zones]
return SeismicFeatureCollection(type="FeatureCollection", features=features)


@router.get("/is-in-seismic-zone", response_model=bool)
async def is_in_seismic_zone(lon: float, lat: float, db: Session = Depends(get_db)):
"""
Check if a point is in a liquefaction zone.
Args:
lon (float): Longitude of the point.
lat (float): Latitude of the point.
db (Session): The database session dependency.
Returns:
bool: True if the point is in a liquefaction zone, False otherwise.
"""
query = db.query(SeismicHazardZone).filter(
SeismicHazardZone.geometry.ST_Contains(
geo_func.ST_SetSRID(geo_func.ST_GeomFromText(f"POINT({lon} {lat})"), 4326)
)
)
return db.query(query.exists()).scalar()
20 changes: 20 additions & 0 deletions backend/api/routers/soft_story_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ..tags import Tags
from sqlalchemy.orm import Session
from backend.database.session import get_db
from geoalchemy2 import functions as geo_func
from backend.api.schemas.soft_story_schemas import (
SoftStoryFeature,
SoftStoryFeatureCollection,
Expand Down Expand Up @@ -39,3 +40,22 @@ async def get_soft_stories(db: Session = Depends(get_db)):

features = [SoftStoryFeature.from_sqlalchemy_model(story) for story in soft_stories]
return SoftStoryFeatureCollection(type="FeatureCollection", features=features)


@router.get("/is-soft-story", response_model=bool)
async def is_soft_story(lon: float, lat: float, db: Session = Depends(get_db)):
"""
Check if a point is a soft story property.

Args:
lon (float): Longitude of the point.
lat (float): Latitude of the point.
db (Session): The database session dependency.

Returns:
bool: True if the point is a soft story property, False otherwise.
"""
query = db.query(SoftStoryProperty).filter(
SoftStoryProperty.point == geo_func.ST_GeomFromText(f"POINT({lon} {lat})", 4326)
)
return db.query(query.exists()).scalar()
22 changes: 22 additions & 0 deletions backend/api/routers/tsunami_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from fastapi import Depends, HTTPException, APIRouter
from ..tags import Tags
from sqlalchemy.orm import Session
from geoalchemy2 import functions as geo_func
from backend.database.session import get_db
from backend.api.schemas.tsunami_schemas import TsunamiFeature, TsunamiFeatureCollection
from backend.api.models.tsunami import TsunamiZone
Expand Down Expand Up @@ -38,3 +39,24 @@ async def get_tsunami_zones(db: Session = Depends(get_db)):
raise HTTPException(status_code=404, detail="No tsunami zones found")
features = [TsunamiFeature.from_sqlalchemy_model(zone) for zone in tsunami_zones]
return TsunamiFeatureCollection(type="FeatureCollection", features=features)


@router.get("/is-in-tsunami-zone", response_model=bool)
async def is_in_tsunami_zone(lon: float, lat: float, db: Session = Depends(get_db)):
"""
Check if a point is in a tsunami zone.

Args:
lon (float): Longitude of the point.
lat (float): Latitude of the point.
db (Session): The database session dependency.

Returns:
bool: True if the point is in a tsunami zone, False otherwise.
"""
query = db.query(TsunamiZone).filter(
TsunamiZone.geometry.ST_Contains(
geo_func.ST_SetSRID(geo_func.ST_GeomFromText(f"POINT({lon} {lat})"), 4326)
)
)
return db.query(query.exists()).scalar()
1 change: 0 additions & 1 deletion backend/api/tests/test_addresses.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest
from backend.api.tests.test_session_config import test_engine, test_session, client


Expand Down
1 change: 0 additions & 1 deletion backend/api/tests/test_landslide.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest
from backend.api.tests.test_session_config import test_engine, test_session, client


Expand Down
18 changes: 17 additions & 1 deletion backend/api/tests/test_liquefaction.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest
from backend.api.tests.test_session_config import test_engine, test_session, client


Expand All @@ -7,3 +6,20 @@ def test_get_liquefaction_zones(client):
response_dict = response.json()
assert response.status_code == 200
assert len(response_dict["features"]) == 3


def test_is_in_liquefaction_zone(client):
lon, lat = [-122.35, 37.83]
response = client.get(
f"/liquefaction-zones/is-in-liquefaction-zone?lon={lon}&lat={lat}"
)
assert response.status_code == 200
assert response.json() # True

# These should not be in liquefaction zones
wrong_lon, wrong_lat = [0.0, 0.0]
response = client.get(
f"/liquefaction-zones/is-in-liquefaction-zone?lon={wrong_lon}&lat={wrong_lat}"
)
assert response.status_code == 200
assert not response.json() # False
16 changes: 15 additions & 1 deletion backend/api/tests/test_seismic.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest
from backend.api.tests.test_session_config import test_engine, test_session, client


Expand All @@ -7,3 +6,18 @@ def test_get_seismic_hazard_zones(client):
response_dict = response.json()
assert response.status_code == 200
assert len(response_dict["features"]) == 2


def test_is_in_seismic_zone(client):
lon, lat = [-122.407436, 37.779759]
response = client.get(f"/seismic-zones/is-in-seismic-zone?lon={lon}&lat={lat}")
assert response.status_code == 200
assert response.json() # True

# These should not be in a seismic hazard zone
wrong_lon, wrong_lat = [0.0, 0.0]
response = client.get(
f"/seismic-zones/is-in-seismic-zone?lon={wrong_lon}&lat={wrong_lat}"
)
assert response.status_code == 200
assert not response.json() # False
1 change: 0 additions & 1 deletion backend/api/tests/test_session_config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from ...api.models.base import Base
from backend.api.config import settings
from fastapi.testclient import TestClient
from ..main import app
Expand Down
16 changes: 15 additions & 1 deletion backend/api/tests/test_soft_story.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import pytest
from backend.api.tests.test_session_config import test_engine, test_session, client


Expand All @@ -7,3 +6,18 @@ def test_get_soft_stories(client):
response_dict = response.json()
assert response.status_code == 200
assert len(response_dict["features"]) == 6


def test_is_soft_story(client):
lon, lat = [-122.424966202, 37.762929444]
response = client.get(f"/soft-stories/is-soft-story?lon={lon}&lat={lat}")
assert response.status_code == 200
assert response.json() # True

# These should not be soft stories
wrong_lon, wrong_lat = [0.0, 0.0]
response = client.get(
f"/soft-stories/is-soft-story?lon={wrong_lon}&lat={wrong_lat}"
)
assert response.status_code == 200
assert not response.json() # False
18 changes: 16 additions & 2 deletions backend/api/tests/test_tsunami.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import pytest
from backend.api.tests.test_session_config import test_engine, test_session, client
from backend.api.tests.test_session_config import test_session, test_engine, client


def test_get_tsunami_zones(client):
response = client.get(f"/tsunami-zones/")
response_dict = response.json()
assert response.status_code == 200
assert len(response_dict["features"]) == 1


def test_is_in_tsunami_zone(client):
lon, lat = [-122.4, 37.75]
response = client.get(f"/tsunami-zones/is-in-tsunami-zone?lon={lon}&lat={lat}")
assert response.status_code == 200
assert response.json() # True

# These should not be in our tsunami zone
wrong_lon, wrong_lat = [0.0, 0.0]
response = client.get(
f"/tsunami-zones/is-in-tsunami-zone?lon={wrong_lon}&lat={wrong_lat}"
)
assert response.status_code == 200
assert not response.json() # False
7 changes: 0 additions & 7 deletions backend/database/init_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,8 @@
"""

from backend.api.models.base import Base
from sqlalchemy.orm import Session
from sqlalchemy import inspect
from backend.database.session import engine
from backend.api.models.addresses import Address
from backend.api.models.tsunami import TsunamiZone
from backend.api.models.landslide_zones import LandslideZone
from backend.api.models.seismic_hazard_zones import SeismicHazardZone
from backend.api.models.liquefaction_zones import LiquefactionZone
from backend.api.models.soft_story_properties import SoftStoryProperty


def init_db():
Expand Down
2 changes: 0 additions & 2 deletions backend/etl/address_data_handler.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from http.client import HTTPException
from backend.etl.data_handler import DataHandler
from backend.api.models.addresses import Address
from shapely.geometry import Point
from geoalchemy2.shape import from_shape, to_shape


ADDRESSES_URL = "https://data.sfgov.org/resource/ramy-di5m.geojson" # This API has a default limit of providing 1,000 rows
Expand Down
2 changes: 1 addition & 1 deletion backend/etl/tsunami_data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from backend.etl.data_handler import DataHandler
from backend.api.models.tsunami import TsunamiZone
from shapely.geometry import Polygon, MultiPolygon
from geoalchemy2.shape import from_shape, to_shape
from geoalchemy2.shape import from_shape


TSUNAMI_URL = "https://services2.arcgis.com/zr3KAIbsRSUyARHG/ArcGIS/rest/services/CA_Tsunami_Hazard_Area/FeatureServer/0/query"
Expand Down
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pandas==2.2.3
pathspec==0.12.1
platformdirs==4.3.6
pluggy==1.5.0
pre_commit==4.0.1
psycopg2-binary==2.9.9
pydantic==2.9.0
pydantic-settings==2.5.2
Expand Down
Loading