From 7b6f3544183e2ad592d30bbc82124ba9b1d6be9d Mon Sep 17 00:00:00 2001 From: Zac Deziel Date: Fri, 19 Jul 2024 11:18:30 -0700 Subject: [PATCH] Implement API design with Postgres Backend (#8) * Initial project structure for API Create fastapi application skeleton with some initial description of the summary endpoint. Includes field valdiation for aoi polygon. * Add summary response with mocked data * Add duckdb data ingestion * Adapt api to read from duckdb * Fix and cleanup h3 generation functionality * Add tests on api * Adapt api for storing data within postgres Load sample data in NYC for development. Modify api to use postgres. Visualization notebook with lonboard for quick QA. * Add configuration variable for table name * Add unit tests for db_utils and update existing API tests Modified app/routers/api.py to utilize get_available_fields and get_summaries from db_utils.py, updated tests/test_api.py to align with the refactored API logic, added app/utils/db_utils.py with utility functions for database operations, including get_available_fields and get_summaries, and added tests/test_db_utils.py with unit tests for the new database utility functions using pytest and unittest.mock to ensure functionality without a real database connection. * Fix definition of environment variables error Add notebook visualization that includes the available fields endpoint. Fix discovered bug in order of environment variables defined before being loaded from env file. * Update field validation to use geojson_pydantic Required shifting some types around for the aoi. Shapely is still used in h3_utils.py. * Remove error handling on HTTPException * Format with black and remove print statements * Add pydantic settings * Update db_utils tests to match new summaries output that reflects df structure * Add github action CI for space2stats api --- .github/workflows/test.yml | 35 ++ .gitignore | 9 +- docker-compose.yaml | 17 + notebooks/summary_data.csv | 11 + notebooks/visualize_nyc_sample.ipynb | 621 ++++++++++++++++++++++++ postgres/chunk_parquet.py | 11 + postgres/download_parquet.sh | 20 + postgres/load_nyc_sample.sh | 45 ++ postgres/load_parquet_chunks.sh | 49 ++ postgres/nyc_sample.py | 29 ++ space2stats_api/__init__.py | 0 space2stats_api/app/main.py | 13 + space2stats_api/app/models/__init__.py | 0 space2stats_api/app/models/summary.py | 0 space2stats_api/app/routers/__init__.py | 0 space2stats_api/app/routers/api.py | 59 +++ space2stats_api/app/settings.py | 15 + space2stats_api/app/utils/__init__.py | 0 space2stats_api/app/utils/db_utils.py | 61 +++ space2stats_api/app/utils/h3_utils.py | 47 ++ space2stats_api/requirements.txt | 12 + space2stats_api/tests/__init__.py | 0 space2stats_api/tests/test_api.py | 77 +++ space2stats_api/tests/test_db_utils.py | 57 +++ space2stats_api/tests/test_h3_utils.py | 41 ++ 25 files changed, 1228 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test.yml create mode 100644 docker-compose.yaml create mode 100644 notebooks/summary_data.csv create mode 100644 notebooks/visualize_nyc_sample.ipynb create mode 100644 postgres/chunk_parquet.py create mode 100644 postgres/download_parquet.sh create mode 100755 postgres/load_nyc_sample.sh create mode 100755 postgres/load_parquet_chunks.sh create mode 100644 postgres/nyc_sample.py create mode 100644 space2stats_api/__init__.py create mode 100644 space2stats_api/app/main.py create mode 100644 space2stats_api/app/models/__init__.py create mode 100644 space2stats_api/app/models/summary.py create mode 100644 space2stats_api/app/routers/__init__.py create mode 100644 space2stats_api/app/routers/api.py create mode 100644 space2stats_api/app/settings.py create mode 100644 space2stats_api/app/utils/__init__.py create mode 100644 space2stats_api/app/utils/db_utils.py create mode 100644 space2stats_api/app/utils/h3_utils.py create mode 100644 space2stats_api/requirements.txt create mode 100644 space2stats_api/tests/__init__.py create mode 100644 space2stats_api/tests/test_api.py create mode 100644 space2stats_api/tests/test_db_utils.py create mode 100644 space2stats_api/tests/test_h3_utils.py diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..81d8e28 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,35 @@ +name: Run Tests + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + + env: + DB_HOST: localhost + DB_PORT: 5432 + DB_NAME: mydatabase + DB_USER: myuser + DB_PASSWORD: mypassword + DB_TABLE_NAME: space2stats + + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.11 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r space2stats_api/requirements.txt + + - name: Set PYTHONPATH + run: echo "PYTHONPATH=$(pwd)/space2stats_api" >> $GITHUB_ENV + + - name: Run tests + run: pytest space2stats_api/tests \ No newline at end of file diff --git a/.gitignore b/.gitignore index f33a4fe..32a76a8 100644 --- a/.gitignore +++ b/.gitignore @@ -93,4 +93,11 @@ target/ _build/ # python-dotenv -.env \ No newline at end of file +.env +wb_aws.env +db.env + +# data +*.parquet +*.duckdb +.pgdata \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..4867b63 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,17 @@ +version: '3' + +services: + database: + image: ghcr.io/stac-utils/pgstac:v0.8.5 + environment: + - POSTGRES_USER=username + - POSTGRES_PASSWORD=password + - POSTGRES_DB=postgis + - PGUSER=username + - PGPASSWORD=password + - PGDATABASE=postgis + ports: + - "${MY_DOCKER_IP:-127.0.0.1}:5439:5432" + command: postgres -N 500 + volumes: + - ./.pgdata:/var/lib/postgresql/data \ No newline at end of file diff --git a/notebooks/summary_data.csv b/notebooks/summary_data.csv new file mode 100644 index 0000000..b831a14 --- /dev/null +++ b/notebooks/summary_data.csv @@ -0,0 +1,11 @@ +hex_id,fields +862a1008fffffff,"{'__index_level_0__': 2436230, 'ogc_fid': 11, 'sum_pop_f_0_2020': 3355.96850585938, 'sum_pop_f_10_2020': 12371.3955078125, 'sum_pop_f_15_2020': 15563.8896484375, 'sum_pop_f_1_2020': 12494.43359375, 'sum_pop_f_20_2020': 30224.130859375, 'sum_pop_f_25_2020': 42427.28125, 'sum_pop_f_30_2020': 34711.5625, 'sum_pop_f_35_2020': 25574.31640625, 'sum_pop_f_40_2020': 20973.458984375, 'sum_pop_f_45_2020': 18116.025390625, 'sum_pop_f_50_2020': 18691.546875, 'sum_pop_f_55_2020': 21246.267578125, 'sum_pop_f_5_2020': 12426.166015625, 'sum_pop_f_60_2020': 22672.314453125, 'sum_pop_f_65_2020': 20404.287109375, 'sum_pop_f_70_2020': 17031.431640625, 'sum_pop_f_75_2020': 11438.015625, 'sum_pop_f_80_2020': 17598.7109375, 'sum_pop_m_0_2020': 3499.36499023438, 'sum_pop_m_10_2020': 12757.32421875, 'sum_pop_m_15_2020': 14690.669921875, 'sum_pop_m_1_2020': 13028.8857421875, 'sum_pop_m_20_2020': 24383.478515625, 'sum_pop_m_25_2020': 36570.5, 'sum_pop_m_30_2020': 33656.46875, 'sum_pop_m_35_2020': 25711.21875, 'sum_pop_m_40_2020': 21449.7265625, 'sum_pop_m_45_2020': 18393.3671875, 'sum_pop_m_50_2020': 17531.7890625, 'sum_pop_m_55_2020': 18519.333984375, 'sum_pop_m_5_2020': 12790.00390625, 'sum_pop_m_60_2020': 17991.046875, 'sum_pop_m_65_2020': 15532.0927734375, 'sum_pop_m_70_2020': 12730.666015625, 'sum_pop_m_75_2020': 8303.8662109375, 'sum_pop_m_80_2020': 9250.68359375}" +862a100d7ffffff,"{'__index_level_0__': 2436238, 'ogc_fid': 19, 'sum_pop_f_0_2020': 3169.98120117188, 'sum_pop_f_10_2020': 11957.44140625, 'sum_pop_f_15_2020': 14855.185546875, 'sum_pop_f_1_2020': 11801.9931640625, 'sum_pop_f_20_2020': 27791.64453125, 'sum_pop_f_25_2020': 38212.46875, 'sum_pop_f_30_2020': 31441.3046875, 'sum_pop_f_35_2020': 23516.759765625, 'sum_pop_f_40_2020': 19418.1640625, 'sum_pop_f_45_2020': 16919.677734375, 'sum_pop_f_50_2020': 17537.130859375, 'sum_pop_f_55_2020': 19871.884765625, 'sum_pop_f_5_2020': 11981.1435546875, 'sum_pop_f_60_2020': 21095.8203125, 'sum_pop_f_65_2020': 18827.03125, 'sum_pop_f_70_2020': 15865.4326171875, 'sum_pop_f_75_2020': 10600.365234375, 'sum_pop_f_80_2020': 16381.328125, 'sum_pop_m_0_2020': 3306.43798828125, 'sum_pop_m_10_2020': 12306.724609375, 'sum_pop_m_15_2020': 14101.4326171875, 'sum_pop_m_1_2020': 12310.578125, 'sum_pop_m_20_2020': 22694.451171875, 'sum_pop_m_25_2020': 32823.91015625, 'sum_pop_m_30_2020': 30206.892578125, 'sum_pop_m_35_2020': 23337.458984375, 'sum_pop_m_40_2020': 19612.8359375, 'sum_pop_m_45_2020': 16889.306640625, 'sum_pop_m_50_2020': 16264.783203125, 'sum_pop_m_55_2020': 17167.7890625, 'sum_pop_m_5_2020': 12373.455078125, 'sum_pop_m_60_2020': 16715.095703125, 'sum_pop_m_65_2020': 14265.3515625, 'sum_pop_m_70_2020': 11801.837890625, 'sum_pop_m_75_2020': 7603.23095703125, 'sum_pop_m_80_2020': 8574.2255859375}" +862a100dfffffff,"{'__index_level_0__': 2436239, 'ogc_fid': 20, 'sum_pop_f_0_2020': 5147.3330078125, 'sum_pop_f_10_2020': 21324.5234375, 'sum_pop_f_15_2020': 22045.63671875, 'sum_pop_f_1_2020': 19163.771484375, 'sum_pop_f_20_2020': 27736.291015625, 'sum_pop_f_25_2020': 35799.5234375, 'sum_pop_f_30_2020': 33238.703125, 'sum_pop_f_35_2020': 27172.52734375, 'sum_pop_f_40_2020': 23106.755859375, 'sum_pop_f_45_2020': 21305.44921875, 'sum_pop_f_50_2020': 22005.82421875, 'sum_pop_f_55_2020': 23561.41796875, 'sum_pop_f_5_2020': 20879.88671875, 'sum_pop_f_60_2020': 23332.68359375, 'sum_pop_f_65_2020': 18850.859375, 'sum_pop_f_70_2020': 17307.85546875, 'sum_pop_f_75_2020': 11436.3251953125, 'sum_pop_f_80_2020': 17651.9609375, 'sum_pop_m_0_2020': 5364.07861328125, 'sum_pop_m_10_2020': 22061.70703125, 'sum_pop_m_15_2020': 22792.357421875, 'sum_pop_m_1_2020': 19971.615234375, 'sum_pop_m_20_2020': 27098.421875, 'sum_pop_m_25_2020': 32599.54296875, 'sum_pop_m_30_2020': 30635.79296875, 'sum_pop_m_35_2020': 24916.66796875, 'sum_pop_m_40_2020': 20631.275390625, 'sum_pop_m_45_2020': 18518.20703125, 'sum_pop_m_50_2020': 18673.708984375, 'sum_pop_m_55_2020': 19300.26953125, 'sum_pop_m_5_2020': 21773.6953125, 'sum_pop_m_60_2020': 18752.81640625, 'sum_pop_m_65_2020': 14246.966796875, 'sum_pop_m_70_2020': 12312.962890625, 'sum_pop_m_75_2020': 7566.3466796875, 'sum_pop_m_80_2020': 8843.8251953125}" +862a10707ffffff,"{'__index_level_0__': 2436390, 'ogc_fid': 49, 'sum_pop_f_0_2020': 1031.58679199219, 'sum_pop_f_10_2020': 3744.32397460938, 'sum_pop_f_15_2020': 3917.22143554688, 'sum_pop_f_1_2020': 3840.6474609375, 'sum_pop_f_20_2020': 5425.830078125, 'sum_pop_f_25_2020': 8483.66796875, 'sum_pop_f_30_2020': 8123.6982421875, 'sum_pop_f_35_2020': 6064.3916015625, 'sum_pop_f_40_2020': 4933.3759765625, 'sum_pop_f_45_2020': 4362.7236328125, 'sum_pop_f_50_2020': 4338.94580078125, 'sum_pop_f_55_2020': 4514.39990234375, 'sum_pop_f_5_2020': 3701.62890625, 'sum_pop_f_60_2020': 4312.689453125, 'sum_pop_f_65_2020': 3729.978515625, 'sum_pop_f_70_2020': 3232.61254882812, 'sum_pop_f_75_2020': 2183.283203125, 'sum_pop_f_80_2020': 3135.68408203125, 'sum_pop_m_0_2020': 1086.77282714844, 'sum_pop_m_10_2020': 3941.2705078125, 'sum_pop_m_15_2020': 4346.45556640625, 'sum_pop_m_1_2020': 4046.28857421875, 'sum_pop_m_20_2020': 5813.85107421875, 'sum_pop_m_25_2020': 8800.37109375, 'sum_pop_m_30_2020': 8521.666015625, 'sum_pop_m_35_2020': 6522.4853515625, 'sum_pop_m_40_2020': 5050.24072265625, 'sum_pop_m_45_2020': 4325.6865234375, 'sum_pop_m_50_2020': 4091.5224609375, 'sum_pop_m_55_2020': 4049.44775390625, 'sum_pop_m_5_2020': 3862.1474609375, 'sum_pop_m_60_2020': 3625.97485351562, 'sum_pop_m_65_2020': 2958.13623046875, 'sum_pop_m_70_2020': 2380.19506835938, 'sum_pop_m_75_2020': 1527.44067382812, 'sum_pop_m_80_2020': 1581.6474609375}" +862a1070fffffff,"{'__index_level_0__': 2436391, 'ogc_fid': 50, 'sum_pop_f_0_2020': 285.267578125, 'sum_pop_f_10_2020': 1234.03344726562, 'sum_pop_f_15_2020': 1272.86743164062, 'sum_pop_f_1_2020': 1062.06518554688, 'sum_pop_f_20_2020': 1501.95458984375, 'sum_pop_f_25_2020': 1971.18774414062, 'sum_pop_f_30_2020': 1970.33056640625, 'sum_pop_f_35_2020': 1658.791015625, 'sum_pop_f_40_2020': 1498.34887695312, 'sum_pop_f_45_2020': 1380.0244140625, 'sum_pop_f_50_2020': 1413.89428710938, 'sum_pop_f_55_2020': 1476.39672851562, 'sum_pop_f_5_2020': 1194.50146484375, 'sum_pop_f_60_2020': 1449.78100585938, 'sum_pop_f_65_2020': 1220.18408203125, 'sum_pop_f_70_2020': 1001.80200195312, 'sum_pop_f_75_2020': 692.449951171875, 'sum_pop_f_80_2020': 1021.06573486328, 'sum_pop_m_0_2020': 301.492889404297, 'sum_pop_m_10_2020': 1309.16052246094, 'sum_pop_m_15_2020': 1371.35522460938, 'sum_pop_m_1_2020': 1122.52270507812, 'sum_pop_m_20_2020': 1590.68212890625, 'sum_pop_m_25_2020': 2043.75939941406, 'sum_pop_m_30_2020': 1984.005859375, 'sum_pop_m_35_2020': 1687.57556152344, 'sum_pop_m_40_2020': 1456.80505371094, 'sum_pop_m_45_2020': 1328.02758789062, 'sum_pop_m_50_2020': 1317.73278808594, 'sum_pop_m_55_2020': 1337.02490234375, 'sum_pop_m_5_2020': 1246.01293945312, 'sum_pop_m_60_2020': 1250.81298828125, 'sum_pop_m_65_2020': 1011.77893066406, 'sum_pop_m_70_2020': 772.69921875, 'sum_pop_m_75_2020': 493.368988037109, 'sum_pop_m_80_2020': 513.383544921875}" +862a10727ffffff,"{'__index_level_0__': 2436394, 'ogc_fid': 53, 'sum_pop_f_0_2020': 1815.30310058594, 'sum_pop_f_10_2020': 6547.26708984375, 'sum_pop_f_15_2020': 7142.46240234375, 'sum_pop_f_1_2020': 6758.46142578125, 'sum_pop_f_20_2020': 10860.0166015625, 'sum_pop_f_25_2020': 16565.375, 'sum_pop_f_30_2020': 15229.623046875, 'sum_pop_f_35_2020': 11273.6337890625, 'sum_pop_f_40_2020': 9142.7578125, 'sum_pop_f_45_2020': 8026.2275390625, 'sum_pop_f_50_2020': 8047.564453125, 'sum_pop_f_55_2020': 8562.94921875, 'sum_pop_f_5_2020': 6503.41015625, 'sum_pop_f_60_2020': 8420.705078125, 'sum_pop_f_65_2020': 7366.7041015625, 'sum_pop_f_70_2020': 6323.734375, 'sum_pop_f_75_2020': 4252.55859375, 'sum_pop_f_80_2020': 6203.60986328125, 'sum_pop_m_0_2020': 1909.22351074219, 'sum_pop_m_10_2020': 6867.8671875, 'sum_pop_m_15_2020': 7672.0263671875, 'sum_pop_m_1_2020': 7108.4482421875, 'sum_pop_m_20_2020': 10829.1796875, 'sum_pop_m_25_2020': 16439.6875, 'sum_pop_m_30_2020': 15721.458984375, 'sum_pop_m_35_2020': 11972.9853515625, 'sum_pop_m_40_2020': 9380.048828125, 'sum_pop_m_45_2020': 8014.259765625, 'sum_pop_m_50_2020': 7586.525390625, 'sum_pop_m_55_2020': 7627.609375, 'sum_pop_m_5_2020': 6769.341796875, 'sum_pop_m_60_2020': 6963.20947265625, 'sum_pop_m_65_2020': 5769.19580078125, 'sum_pop_m_70_2020': 4668.7470703125, 'sum_pop_m_75_2020': 3006.267578125, 'sum_pop_m_80_2020': 3161.91650390625}" +862a1072fffffff,"{'__index_level_0__': 2436395, 'ogc_fid': 54, 'sum_pop_f_0_2020': 1863.88208007812, 'sum_pop_f_10_2020': 7031.26171875, 'sum_pop_f_15_2020': 8313.15625, 'sum_pop_f_1_2020': 6939.32373046875, 'sum_pop_f_20_2020': 14435.01171875, 'sum_pop_f_25_2020': 20009.3125, 'sum_pop_f_30_2020': 16851.1328125, 'sum_pop_f_35_2020': 12630.2119140625, 'sum_pop_f_40_2020': 10353.865234375, 'sum_pop_f_45_2020': 9068.0224609375, 'sum_pop_f_50_2020': 9348.8232421875, 'sum_pop_f_55_2020': 10468.044921875, 'sum_pop_f_5_2020': 7024.83740234375, 'sum_pop_f_60_2020': 10943.9521484375, 'sum_pop_f_65_2020': 9579.228515625, 'sum_pop_f_70_2020': 8196.7197265625, 'sum_pop_f_75_2020': 5452.208984375, 'sum_pop_f_80_2020': 8342.0283203125, 'sum_pop_m_0_2020': 1944.47668457031, 'sum_pop_m_10_2020': 7258.96826171875, 'sum_pop_m_15_2020': 8105.0302734375, 'sum_pop_m_1_2020': 7239.70361328125, 'sum_pop_m_20_2020': 12277.5703125, 'sum_pop_m_25_2020': 17537.927734375, 'sum_pop_m_30_2020': 16196.8681640625, 'sum_pop_m_35_2020': 12452.357421875, 'sum_pop_m_40_2020': 10254.236328125, 'sum_pop_m_45_2020': 8837.01171875, 'sum_pop_m_50_2020': 8528.3359375, 'sum_pop_m_55_2020': 8954.818359375, 'sum_pop_m_5_2020': 7267.75244140625, 'sum_pop_m_60_2020': 8682.8642578125, 'sum_pop_m_65_2020': 7253.91015625, 'sum_pop_m_70_2020': 6027.0146484375, 'sum_pop_m_75_2020': 3859.2119140625, 'sum_pop_m_80_2020': 4310.0849609375}" +862a10757ffffff,"{'__index_level_0__': 2436399, 'ogc_fid': 58, 'sum_pop_f_0_2020': 834.680480957031, 'sum_pop_f_10_2020': 3883.02319335938, 'sum_pop_f_15_2020': 3978.943359375, 'sum_pop_f_1_2020': 3107.55615234375, 'sum_pop_f_20_2020': 4433.38232421875, 'sum_pop_f_25_2020': 5126.501953125, 'sum_pop_f_30_2020': 5110.86669921875, 'sum_pop_f_35_2020': 4612.77783203125, 'sum_pop_f_40_2020': 4320.1806640625, 'sum_pop_f_45_2020': 4065.07666015625, 'sum_pop_f_50_2020': 4238.0830078125, 'sum_pop_f_55_2020': 4474.5029296875, 'sum_pop_f_5_2020': 3739.5732421875, 'sum_pop_f_60_2020': 4464.12255859375, 'sum_pop_f_65_2020': 3650.60009765625, 'sum_pop_f_70_2020': 3047.7119140625, 'sum_pop_f_75_2020': 2093.39038085938, 'sum_pop_f_80_2020': 3182.42626953125, 'sum_pop_m_0_2020': 878.235229492188, 'sum_pop_m_10_2020': 4092.75439453125, 'sum_pop_m_15_2020': 4156.81689453125, 'sum_pop_m_1_2020': 3269.85815429688, 'sum_pop_m_20_2020': 4496.69580078125, 'sum_pop_m_25_2020': 4984.02490234375, 'sum_pop_m_30_2020': 4775.9384765625, 'sum_pop_m_35_2020': 4325.20068359375, 'sum_pop_m_40_2020': 3944.751953125, 'sum_pop_m_45_2020': 3706.37255859375, 'sum_pop_m_50_2020': 3792.64916992188, 'sum_pop_m_55_2020': 3921.2607421875, 'sum_pop_m_5_2020': 3899.29052734375, 'sum_pop_m_60_2020': 3796.07446289062, 'sum_pop_m_65_2020': 2990.9345703125, 'sum_pop_m_70_2020': 2328.72338867188, 'sum_pop_m_75_2020': 1461.92907714844, 'sum_pop_m_80_2020': 1593.95361328125}" +862a10767ffffff,"{'__index_level_0__': 2436401, 'ogc_fid': 60, 'sum_pop_f_0_2020': 3148.86889648438, 'sum_pop_f_10_2020': 13023.1484375, 'sum_pop_f_15_2020': 13454.26953125, 'sum_pop_f_1_2020': 11723.392578125, 'sum_pop_f_20_2020': 16926.83203125, 'sum_pop_f_25_2020': 21846.34375, 'sum_pop_f_30_2020': 20248.33984375, 'sum_pop_f_35_2020': 16524.6953125, 'sum_pop_f_40_2020': 14018.3984375, 'sum_pop_f_45_2020': 12919.609375, 'sum_pop_f_50_2020': 13344.369140625, 'sum_pop_f_55_2020': 14300.04296875, 'sum_pop_f_5_2020': 12757.994140625, 'sum_pop_f_60_2020': 14169.640625, 'sum_pop_f_65_2020': 11414.6669921875, 'sum_pop_f_70_2020': 10501.298828125, 'sum_pop_f_75_2020': 6928.44677734375, 'sum_pop_f_80_2020': 10685.734375, 'sum_pop_m_0_2020': 3280.53686523438, 'sum_pop_m_10_2020': 13470.740234375, 'sum_pop_m_15_2020': 13894.583984375, 'sum_pop_m_1_2020': 12214.142578125, 'sum_pop_m_20_2020': 16504.916015625, 'sum_pop_m_25_2020': 19838.443359375, 'sum_pop_m_30_2020': 18610.52734375, 'sum_pop_m_35_2020': 15098.4765625, 'sum_pop_m_40_2020': 12466.5986328125, 'sum_pop_m_45_2020': 11185.37109375, 'sum_pop_m_50_2020': 11283.837890625, 'sum_pop_m_55_2020': 11677.3203125, 'sum_pop_m_5_2020': 13300.9453125, 'sum_pop_m_60_2020': 11366.205078125, 'sum_pop_m_65_2020': 8611.40625, 'sum_pop_m_70_2020': 7453.0439453125, 'sum_pop_m_75_2020': 4575.9609375, 'sum_pop_m_80_2020': 5342.28955078125}" +862a10777ffffff,"{'__index_level_0__': 2436403, 'ogc_fid': 62, 'sum_pop_f_0_2020': 3401.08959960938, 'sum_pop_f_10_2020': 14066.287109375, 'sum_pop_f_15_2020': 14531.94140625, 'sum_pop_f_1_2020': 12662.421875, 'sum_pop_f_20_2020': 18282.650390625, 'sum_pop_f_25_2020': 23596.2109375, 'sum_pop_f_30_2020': 21870.20703125, 'sum_pop_f_35_2020': 17848.302734375, 'sum_pop_f_40_2020': 15141.2568359375, 'sum_pop_f_45_2020': 13954.455078125, 'sum_pop_f_50_2020': 14413.2373046875, 'sum_pop_f_55_2020': 15445.458984375, 'sum_pop_f_5_2020': 13779.89453125, 'sum_pop_f_60_2020': 15304.611328125, 'sum_pop_f_65_2020': 12328.96875, 'sum_pop_f_70_2020': 11342.439453125, 'sum_pop_f_75_2020': 7483.4072265625, 'sum_pop_f_80_2020': 11541.6484375, 'sum_pop_m_0_2020': 3543.30419921875, 'sum_pop_m_10_2020': 14549.73046875, 'sum_pop_m_15_2020': 15007.5234375, 'sum_pop_m_1_2020': 13192.482421875, 'sum_pop_m_20_2020': 17826.94140625, 'sum_pop_m_25_2020': 21427.48046875, 'sum_pop_m_30_2020': 20101.208984375, 'sum_pop_m_35_2020': 16307.8466796875, 'sum_pop_m_40_2020': 13465.1591796875, 'sum_pop_m_45_2020': 12081.306640625, 'sum_pop_m_50_2020': 12187.66015625, 'sum_pop_m_55_2020': 12612.66015625, 'sum_pop_m_5_2020': 14366.3359375, 'sum_pop_m_60_2020': 12276.625, 'sum_pop_m_65_2020': 9301.1689453125, 'sum_pop_m_70_2020': 8050.02490234375, 'sum_pop_m_75_2020': 4942.490234375, 'sum_pop_m_80_2020': 5770.201171875}" diff --git a/notebooks/visualize_nyc_sample.ipynb b/notebooks/visualize_nyc_sample.ipynb new file mode 100644 index 0000000..258d951 --- /dev/null +++ b/notebooks/visualize_nyc_sample.ipynb @@ -0,0 +1,621 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: geojson_pydantic in /Users/zacdez/Library/Caches/pypoetry/virtualenvs/notebooks-sNYx7QfP-py3.12/lib/python3.12/site-packages (1.1.0)\n", + "Requirement already satisfied: pydantic~=2.0 in /Users/zacdez/Library/Caches/pypoetry/virtualenvs/notebooks-sNYx7QfP-py3.12/lib/python3.12/site-packages (from geojson_pydantic) (2.7.0)\n", + "Requirement already satisfied: annotated-types>=0.4.0 in /Users/zacdez/Library/Caches/pypoetry/virtualenvs/notebooks-sNYx7QfP-py3.12/lib/python3.12/site-packages (from pydantic~=2.0->geojson_pydantic) (0.6.0)\n", + "Requirement already satisfied: pydantic-core==2.18.1 in /Users/zacdez/Library/Caches/pypoetry/virtualenvs/notebooks-sNYx7QfP-py3.12/lib/python3.12/site-packages (from pydantic~=2.0->geojson_pydantic) (2.18.1)\n", + "Requirement already satisfied: typing-extensions>=4.6.1 in /Users/zacdez/Library/Caches/pypoetry/virtualenvs/notebooks-sNYx7QfP-py3.12/lib/python3.12/site-packages (from pydantic~=2.0->geojson_pydantic) (4.9.0)\n", + "\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.1.2\u001b[0m\n", + "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip install geojson_pydantic" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import requests\n", + "import pandas as pd\n", + "import geopandas as gpd\n", + "from lonboard import viz\n", + "import h3" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "BASE_URL = \"http://localhost:8000\"\n", + "FIELDS_ENDPOINT = f\"{BASE_URL}/fields\"\n", + "SUMMARY_ENDPOINT = f\"{BASE_URL}/summary\"" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Available Fields: ['__index_level_0__', 'ogc_fid', 'sum_pop_f_0_2020', 'sum_pop_f_10_2020', 'sum_pop_f_15_2020', 'sum_pop_f_1_2020', 'sum_pop_f_20_2020', 'sum_pop_f_25_2020', 'sum_pop_f_30_2020', 'sum_pop_f_35_2020', 'sum_pop_f_40_2020', 'sum_pop_f_45_2020', 'sum_pop_f_50_2020', 'sum_pop_f_55_2020', 'sum_pop_f_5_2020', 'sum_pop_f_60_2020', 'sum_pop_f_65_2020', 'sum_pop_f_70_2020', 'sum_pop_f_75_2020', 'sum_pop_f_80_2020', 'sum_pop_m_0_2020', 'sum_pop_m_10_2020', 'sum_pop_m_15_2020', 'sum_pop_m_1_2020', 'sum_pop_m_20_2020', 'sum_pop_m_25_2020', 'sum_pop_m_30_2020', 'sum_pop_m_35_2020', 'sum_pop_m_40_2020', 'sum_pop_m_45_2020', 'sum_pop_m_50_2020', 'sum_pop_m_55_2020', 'sum_pop_m_5_2020', 'sum_pop_m_60_2020', 'sum_pop_m_65_2020', 'sum_pop_m_70_2020', 'sum_pop_m_75_2020', 'sum_pop_m_80_2020']\n" + ] + } + ], + "source": [ + "response = requests.get(FIELDS_ENDPOINT)\n", + "if response.status_code != 200:\n", + " raise Exception(f\"Failed to get fields: {response.text}\")\n", + "\n", + "available_fields = response.json()\n", + "print(\"Available Fields:\", available_fields)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [], + "source": [ + "from typing import List, Dict, Any, Literal\n", + "\n", + "from geojson_pydantic import Feature, Polygon\n", + "\n", + "AOIModel = Feature[Polygon, Dict]\n", + "\n", + "aoi = {\n", + " \"type\": \"Feature\",\n", + " \"geometry\": {\n", + " \"type\": \"Polygon\",\n", + " \"coordinates\": [\n", + " [\n", + " [-74.1, 40.6],\n", + " [-73.9, 40.6],\n", + " [-73.9, 40.8],\n", + " [-74.1, 40.8],\n", + " [-74.1, 40.6]\n", + " ]\n", + " ]\n", + " },\n", + " \"properties\": {}\n", + "}\n", + "\n", + "feat = AOIModel(**aoi)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Summary Data: [{'hex_id': '862a1008fffffff', '__index_level_0__': 2436230, 'ogc_fid': 11, 'sum_pop_f_0_2020': 3355.96850585938, 'sum_pop_f_10_2020': 12371.3955078125, 'sum_pop_f_15_2020': 15563.8896484375, 'sum_pop_f_1_2020': 12494.43359375, 'sum_pop_f_20_2020': 30224.130859375, 'sum_pop_f_25_2020': 42427.28125, 'sum_pop_f_30_2020': 34711.5625, 'sum_pop_f_35_2020': 25574.31640625, 'sum_pop_f_40_2020': 20973.458984375, 'sum_pop_f_45_2020': 18116.025390625, 'sum_pop_f_50_2020': 18691.546875, 'sum_pop_f_55_2020': 21246.267578125, 'sum_pop_f_5_2020': 12426.166015625, 'sum_pop_f_60_2020': 22672.314453125, 'sum_pop_f_65_2020': 20404.287109375, 'sum_pop_f_70_2020': 17031.431640625, 'sum_pop_f_75_2020': 11438.015625, 'sum_pop_f_80_2020': 17598.7109375, 'sum_pop_m_0_2020': 3499.36499023438, 'sum_pop_m_10_2020': 12757.32421875, 'sum_pop_m_15_2020': 14690.669921875, 'sum_pop_m_1_2020': 13028.8857421875, 'sum_pop_m_20_2020': 24383.478515625, 'sum_pop_m_25_2020': 36570.5, 'sum_pop_m_30_2020': 33656.46875, 'sum_pop_m_35_2020': 25711.21875, 'sum_pop_m_40_2020': 21449.7265625, 'sum_pop_m_45_2020': 18393.3671875, 'sum_pop_m_50_2020': 17531.7890625, 'sum_pop_m_55_2020': 18519.333984375, 'sum_pop_m_5_2020': 12790.00390625, 'sum_pop_m_60_2020': 17991.046875, 'sum_pop_m_65_2020': 15532.0927734375, 'sum_pop_m_70_2020': 12730.666015625, 'sum_pop_m_75_2020': 8303.8662109375, 'sum_pop_m_80_2020': 9250.68359375}, {'hex_id': '862a100d7ffffff', '__index_level_0__': 2436238, 'ogc_fid': 19, 'sum_pop_f_0_2020': 3169.98120117188, 'sum_pop_f_10_2020': 11957.44140625, 'sum_pop_f_15_2020': 14855.185546875, 'sum_pop_f_1_2020': 11801.9931640625, 'sum_pop_f_20_2020': 27791.64453125, 'sum_pop_f_25_2020': 38212.46875, 'sum_pop_f_30_2020': 31441.3046875, 'sum_pop_f_35_2020': 23516.759765625, 'sum_pop_f_40_2020': 19418.1640625, 'sum_pop_f_45_2020': 16919.677734375, 'sum_pop_f_50_2020': 17537.130859375, 'sum_pop_f_55_2020': 19871.884765625, 'sum_pop_f_5_2020': 11981.1435546875, 'sum_pop_f_60_2020': 21095.8203125, 'sum_pop_f_65_2020': 18827.03125, 'sum_pop_f_70_2020': 15865.4326171875, 'sum_pop_f_75_2020': 10600.365234375, 'sum_pop_f_80_2020': 16381.328125, 'sum_pop_m_0_2020': 3306.43798828125, 'sum_pop_m_10_2020': 12306.724609375, 'sum_pop_m_15_2020': 14101.4326171875, 'sum_pop_m_1_2020': 12310.578125, 'sum_pop_m_20_2020': 22694.451171875, 'sum_pop_m_25_2020': 32823.91015625, 'sum_pop_m_30_2020': 30206.892578125, 'sum_pop_m_35_2020': 23337.458984375, 'sum_pop_m_40_2020': 19612.8359375, 'sum_pop_m_45_2020': 16889.306640625, 'sum_pop_m_50_2020': 16264.783203125, 'sum_pop_m_55_2020': 17167.7890625, 'sum_pop_m_5_2020': 12373.455078125, 'sum_pop_m_60_2020': 16715.095703125, 'sum_pop_m_65_2020': 14265.3515625, 'sum_pop_m_70_2020': 11801.837890625, 'sum_pop_m_75_2020': 7603.23095703125, 'sum_pop_m_80_2020': 8574.2255859375}, {'hex_id': '862a100dfffffff', '__index_level_0__': 2436239, 'ogc_fid': 20, 'sum_pop_f_0_2020': 5147.3330078125, 'sum_pop_f_10_2020': 21324.5234375, 'sum_pop_f_15_2020': 22045.63671875, 'sum_pop_f_1_2020': 19163.771484375, 'sum_pop_f_20_2020': 27736.291015625, 'sum_pop_f_25_2020': 35799.5234375, 'sum_pop_f_30_2020': 33238.703125, 'sum_pop_f_35_2020': 27172.52734375, 'sum_pop_f_40_2020': 23106.755859375, 'sum_pop_f_45_2020': 21305.44921875, 'sum_pop_f_50_2020': 22005.82421875, 'sum_pop_f_55_2020': 23561.41796875, 'sum_pop_f_5_2020': 20879.88671875, 'sum_pop_f_60_2020': 23332.68359375, 'sum_pop_f_65_2020': 18850.859375, 'sum_pop_f_70_2020': 17307.85546875, 'sum_pop_f_75_2020': 11436.3251953125, 'sum_pop_f_80_2020': 17651.9609375, 'sum_pop_m_0_2020': 5364.07861328125, 'sum_pop_m_10_2020': 22061.70703125, 'sum_pop_m_15_2020': 22792.357421875, 'sum_pop_m_1_2020': 19971.615234375, 'sum_pop_m_20_2020': 27098.421875, 'sum_pop_m_25_2020': 32599.54296875, 'sum_pop_m_30_2020': 30635.79296875, 'sum_pop_m_35_2020': 24916.66796875, 'sum_pop_m_40_2020': 20631.275390625, 'sum_pop_m_45_2020': 18518.20703125, 'sum_pop_m_50_2020': 18673.708984375, 'sum_pop_m_55_2020': 19300.26953125, 'sum_pop_m_5_2020': 21773.6953125, 'sum_pop_m_60_2020': 18752.81640625, 'sum_pop_m_65_2020': 14246.966796875, 'sum_pop_m_70_2020': 12312.962890625, 'sum_pop_m_75_2020': 7566.3466796875, 'sum_pop_m_80_2020': 8843.8251953125}, {'hex_id': '862a10707ffffff', '__index_level_0__': 2436390, 'ogc_fid': 49, 'sum_pop_f_0_2020': 1031.58679199219, 'sum_pop_f_10_2020': 3744.32397460938, 'sum_pop_f_15_2020': 3917.22143554688, 'sum_pop_f_1_2020': 3840.6474609375, 'sum_pop_f_20_2020': 5425.830078125, 'sum_pop_f_25_2020': 8483.66796875, 'sum_pop_f_30_2020': 8123.6982421875, 'sum_pop_f_35_2020': 6064.3916015625, 'sum_pop_f_40_2020': 4933.3759765625, 'sum_pop_f_45_2020': 4362.7236328125, 'sum_pop_f_50_2020': 4338.94580078125, 'sum_pop_f_55_2020': 4514.39990234375, 'sum_pop_f_5_2020': 3701.62890625, 'sum_pop_f_60_2020': 4312.689453125, 'sum_pop_f_65_2020': 3729.978515625, 'sum_pop_f_70_2020': 3232.61254882812, 'sum_pop_f_75_2020': 2183.283203125, 'sum_pop_f_80_2020': 3135.68408203125, 'sum_pop_m_0_2020': 1086.77282714844, 'sum_pop_m_10_2020': 3941.2705078125, 'sum_pop_m_15_2020': 4346.45556640625, 'sum_pop_m_1_2020': 4046.28857421875, 'sum_pop_m_20_2020': 5813.85107421875, 'sum_pop_m_25_2020': 8800.37109375, 'sum_pop_m_30_2020': 8521.666015625, 'sum_pop_m_35_2020': 6522.4853515625, 'sum_pop_m_40_2020': 5050.24072265625, 'sum_pop_m_45_2020': 4325.6865234375, 'sum_pop_m_50_2020': 4091.5224609375, 'sum_pop_m_55_2020': 4049.44775390625, 'sum_pop_m_5_2020': 3862.1474609375, 'sum_pop_m_60_2020': 3625.97485351562, 'sum_pop_m_65_2020': 2958.13623046875, 'sum_pop_m_70_2020': 2380.19506835938, 'sum_pop_m_75_2020': 1527.44067382812, 'sum_pop_m_80_2020': 1581.6474609375}, {'hex_id': '862a1070fffffff', '__index_level_0__': 2436391, 'ogc_fid': 50, 'sum_pop_f_0_2020': 285.267578125, 'sum_pop_f_10_2020': 1234.03344726562, 'sum_pop_f_15_2020': 1272.86743164062, 'sum_pop_f_1_2020': 1062.06518554688, 'sum_pop_f_20_2020': 1501.95458984375, 'sum_pop_f_25_2020': 1971.18774414062, 'sum_pop_f_30_2020': 1970.33056640625, 'sum_pop_f_35_2020': 1658.791015625, 'sum_pop_f_40_2020': 1498.34887695312, 'sum_pop_f_45_2020': 1380.0244140625, 'sum_pop_f_50_2020': 1413.89428710938, 'sum_pop_f_55_2020': 1476.39672851562, 'sum_pop_f_5_2020': 1194.50146484375, 'sum_pop_f_60_2020': 1449.78100585938, 'sum_pop_f_65_2020': 1220.18408203125, 'sum_pop_f_70_2020': 1001.80200195312, 'sum_pop_f_75_2020': 692.449951171875, 'sum_pop_f_80_2020': 1021.06573486328, 'sum_pop_m_0_2020': 301.492889404297, 'sum_pop_m_10_2020': 1309.16052246094, 'sum_pop_m_15_2020': 1371.35522460938, 'sum_pop_m_1_2020': 1122.52270507812, 'sum_pop_m_20_2020': 1590.68212890625, 'sum_pop_m_25_2020': 2043.75939941406, 'sum_pop_m_30_2020': 1984.005859375, 'sum_pop_m_35_2020': 1687.57556152344, 'sum_pop_m_40_2020': 1456.80505371094, 'sum_pop_m_45_2020': 1328.02758789062, 'sum_pop_m_50_2020': 1317.73278808594, 'sum_pop_m_55_2020': 1337.02490234375, 'sum_pop_m_5_2020': 1246.01293945312, 'sum_pop_m_60_2020': 1250.81298828125, 'sum_pop_m_65_2020': 1011.77893066406, 'sum_pop_m_70_2020': 772.69921875, 'sum_pop_m_75_2020': 493.368988037109, 'sum_pop_m_80_2020': 513.383544921875}, {'hex_id': '862a10727ffffff', '__index_level_0__': 2436394, 'ogc_fid': 53, 'sum_pop_f_0_2020': 1815.30310058594, 'sum_pop_f_10_2020': 6547.26708984375, 'sum_pop_f_15_2020': 7142.46240234375, 'sum_pop_f_1_2020': 6758.46142578125, 'sum_pop_f_20_2020': 10860.0166015625, 'sum_pop_f_25_2020': 16565.375, 'sum_pop_f_30_2020': 15229.623046875, 'sum_pop_f_35_2020': 11273.6337890625, 'sum_pop_f_40_2020': 9142.7578125, 'sum_pop_f_45_2020': 8026.2275390625, 'sum_pop_f_50_2020': 8047.564453125, 'sum_pop_f_55_2020': 8562.94921875, 'sum_pop_f_5_2020': 6503.41015625, 'sum_pop_f_60_2020': 8420.705078125, 'sum_pop_f_65_2020': 7366.7041015625, 'sum_pop_f_70_2020': 6323.734375, 'sum_pop_f_75_2020': 4252.55859375, 'sum_pop_f_80_2020': 6203.60986328125, 'sum_pop_m_0_2020': 1909.22351074219, 'sum_pop_m_10_2020': 6867.8671875, 'sum_pop_m_15_2020': 7672.0263671875, 'sum_pop_m_1_2020': 7108.4482421875, 'sum_pop_m_20_2020': 10829.1796875, 'sum_pop_m_25_2020': 16439.6875, 'sum_pop_m_30_2020': 15721.458984375, 'sum_pop_m_35_2020': 11972.9853515625, 'sum_pop_m_40_2020': 9380.048828125, 'sum_pop_m_45_2020': 8014.259765625, 'sum_pop_m_50_2020': 7586.525390625, 'sum_pop_m_55_2020': 7627.609375, 'sum_pop_m_5_2020': 6769.341796875, 'sum_pop_m_60_2020': 6963.20947265625, 'sum_pop_m_65_2020': 5769.19580078125, 'sum_pop_m_70_2020': 4668.7470703125, 'sum_pop_m_75_2020': 3006.267578125, 'sum_pop_m_80_2020': 3161.91650390625}, {'hex_id': '862a1072fffffff', '__index_level_0__': 2436395, 'ogc_fid': 54, 'sum_pop_f_0_2020': 1863.88208007812, 'sum_pop_f_10_2020': 7031.26171875, 'sum_pop_f_15_2020': 8313.15625, 'sum_pop_f_1_2020': 6939.32373046875, 'sum_pop_f_20_2020': 14435.01171875, 'sum_pop_f_25_2020': 20009.3125, 'sum_pop_f_30_2020': 16851.1328125, 'sum_pop_f_35_2020': 12630.2119140625, 'sum_pop_f_40_2020': 10353.865234375, 'sum_pop_f_45_2020': 9068.0224609375, 'sum_pop_f_50_2020': 9348.8232421875, 'sum_pop_f_55_2020': 10468.044921875, 'sum_pop_f_5_2020': 7024.83740234375, 'sum_pop_f_60_2020': 10943.9521484375, 'sum_pop_f_65_2020': 9579.228515625, 'sum_pop_f_70_2020': 8196.7197265625, 'sum_pop_f_75_2020': 5452.208984375, 'sum_pop_f_80_2020': 8342.0283203125, 'sum_pop_m_0_2020': 1944.47668457031, 'sum_pop_m_10_2020': 7258.96826171875, 'sum_pop_m_15_2020': 8105.0302734375, 'sum_pop_m_1_2020': 7239.70361328125, 'sum_pop_m_20_2020': 12277.5703125, 'sum_pop_m_25_2020': 17537.927734375, 'sum_pop_m_30_2020': 16196.8681640625, 'sum_pop_m_35_2020': 12452.357421875, 'sum_pop_m_40_2020': 10254.236328125, 'sum_pop_m_45_2020': 8837.01171875, 'sum_pop_m_50_2020': 8528.3359375, 'sum_pop_m_55_2020': 8954.818359375, 'sum_pop_m_5_2020': 7267.75244140625, 'sum_pop_m_60_2020': 8682.8642578125, 'sum_pop_m_65_2020': 7253.91015625, 'sum_pop_m_70_2020': 6027.0146484375, 'sum_pop_m_75_2020': 3859.2119140625, 'sum_pop_m_80_2020': 4310.0849609375}, {'hex_id': '862a10757ffffff', '__index_level_0__': 2436399, 'ogc_fid': 58, 'sum_pop_f_0_2020': 834.680480957031, 'sum_pop_f_10_2020': 3883.02319335938, 'sum_pop_f_15_2020': 3978.943359375, 'sum_pop_f_1_2020': 3107.55615234375, 'sum_pop_f_20_2020': 4433.38232421875, 'sum_pop_f_25_2020': 5126.501953125, 'sum_pop_f_30_2020': 5110.86669921875, 'sum_pop_f_35_2020': 4612.77783203125, 'sum_pop_f_40_2020': 4320.1806640625, 'sum_pop_f_45_2020': 4065.07666015625, 'sum_pop_f_50_2020': 4238.0830078125, 'sum_pop_f_55_2020': 4474.5029296875, 'sum_pop_f_5_2020': 3739.5732421875, 'sum_pop_f_60_2020': 4464.12255859375, 'sum_pop_f_65_2020': 3650.60009765625, 'sum_pop_f_70_2020': 3047.7119140625, 'sum_pop_f_75_2020': 2093.39038085938, 'sum_pop_f_80_2020': 3182.42626953125, 'sum_pop_m_0_2020': 878.235229492188, 'sum_pop_m_10_2020': 4092.75439453125, 'sum_pop_m_15_2020': 4156.81689453125, 'sum_pop_m_1_2020': 3269.85815429688, 'sum_pop_m_20_2020': 4496.69580078125, 'sum_pop_m_25_2020': 4984.02490234375, 'sum_pop_m_30_2020': 4775.9384765625, 'sum_pop_m_35_2020': 4325.20068359375, 'sum_pop_m_40_2020': 3944.751953125, 'sum_pop_m_45_2020': 3706.37255859375, 'sum_pop_m_50_2020': 3792.64916992188, 'sum_pop_m_55_2020': 3921.2607421875, 'sum_pop_m_5_2020': 3899.29052734375, 'sum_pop_m_60_2020': 3796.07446289062, 'sum_pop_m_65_2020': 2990.9345703125, 'sum_pop_m_70_2020': 2328.72338867188, 'sum_pop_m_75_2020': 1461.92907714844, 'sum_pop_m_80_2020': 1593.95361328125}, {'hex_id': '862a10767ffffff', '__index_level_0__': 2436401, 'ogc_fid': 60, 'sum_pop_f_0_2020': 3148.86889648438, 'sum_pop_f_10_2020': 13023.1484375, 'sum_pop_f_15_2020': 13454.26953125, 'sum_pop_f_1_2020': 11723.392578125, 'sum_pop_f_20_2020': 16926.83203125, 'sum_pop_f_25_2020': 21846.34375, 'sum_pop_f_30_2020': 20248.33984375, 'sum_pop_f_35_2020': 16524.6953125, 'sum_pop_f_40_2020': 14018.3984375, 'sum_pop_f_45_2020': 12919.609375, 'sum_pop_f_50_2020': 13344.369140625, 'sum_pop_f_55_2020': 14300.04296875, 'sum_pop_f_5_2020': 12757.994140625, 'sum_pop_f_60_2020': 14169.640625, 'sum_pop_f_65_2020': 11414.6669921875, 'sum_pop_f_70_2020': 10501.298828125, 'sum_pop_f_75_2020': 6928.44677734375, 'sum_pop_f_80_2020': 10685.734375, 'sum_pop_m_0_2020': 3280.53686523438, 'sum_pop_m_10_2020': 13470.740234375, 'sum_pop_m_15_2020': 13894.583984375, 'sum_pop_m_1_2020': 12214.142578125, 'sum_pop_m_20_2020': 16504.916015625, 'sum_pop_m_25_2020': 19838.443359375, 'sum_pop_m_30_2020': 18610.52734375, 'sum_pop_m_35_2020': 15098.4765625, 'sum_pop_m_40_2020': 12466.5986328125, 'sum_pop_m_45_2020': 11185.37109375, 'sum_pop_m_50_2020': 11283.837890625, 'sum_pop_m_55_2020': 11677.3203125, 'sum_pop_m_5_2020': 13300.9453125, 'sum_pop_m_60_2020': 11366.205078125, 'sum_pop_m_65_2020': 8611.40625, 'sum_pop_m_70_2020': 7453.0439453125, 'sum_pop_m_75_2020': 4575.9609375, 'sum_pop_m_80_2020': 5342.28955078125}, {'hex_id': '862a10777ffffff', '__index_level_0__': 2436403, 'ogc_fid': 62, 'sum_pop_f_0_2020': 3401.08959960938, 'sum_pop_f_10_2020': 14066.287109375, 'sum_pop_f_15_2020': 14531.94140625, 'sum_pop_f_1_2020': 12662.421875, 'sum_pop_f_20_2020': 18282.650390625, 'sum_pop_f_25_2020': 23596.2109375, 'sum_pop_f_30_2020': 21870.20703125, 'sum_pop_f_35_2020': 17848.302734375, 'sum_pop_f_40_2020': 15141.2568359375, 'sum_pop_f_45_2020': 13954.455078125, 'sum_pop_f_50_2020': 14413.2373046875, 'sum_pop_f_55_2020': 15445.458984375, 'sum_pop_f_5_2020': 13779.89453125, 'sum_pop_f_60_2020': 15304.611328125, 'sum_pop_f_65_2020': 12328.96875, 'sum_pop_f_70_2020': 11342.439453125, 'sum_pop_f_75_2020': 7483.4072265625, 'sum_pop_f_80_2020': 11541.6484375, 'sum_pop_m_0_2020': 3543.30419921875, 'sum_pop_m_10_2020': 14549.73046875, 'sum_pop_m_15_2020': 15007.5234375, 'sum_pop_m_1_2020': 13192.482421875, 'sum_pop_m_20_2020': 17826.94140625, 'sum_pop_m_25_2020': 21427.48046875, 'sum_pop_m_30_2020': 20101.208984375, 'sum_pop_m_35_2020': 16307.8466796875, 'sum_pop_m_40_2020': 13465.1591796875, 'sum_pop_m_45_2020': 12081.306640625, 'sum_pop_m_50_2020': 12187.66015625, 'sum_pop_m_55_2020': 12612.66015625, 'sum_pop_m_5_2020': 14366.3359375, 'sum_pop_m_60_2020': 12276.625, 'sum_pop_m_65_2020': 9301.1689453125, 'sum_pop_m_70_2020': 8050.02490234375, 'sum_pop_m_75_2020': 4942.490234375, 'sum_pop_m_80_2020': 5770.201171875}]\n" + ] + } + ], + "source": [ + "aoi = {\n", + " \"type\": \"Feature\",\n", + " \"geometry\": {\n", + " \"type\": \"Polygon\",\n", + " \"coordinates\": [\n", + " [\n", + " [-74.1, 40.6],\n", + " [-73.9, 40.6],\n", + " [-73.9, 40.8],\n", + " [-74.1, 40.8],\n", + " [-74.1, 40.6]\n", + " ]\n", + " ]\n", + " },\n", + " \"properties\": {}\n", + "\n", + "}\n", + "\n", + "# Define the Request Payload\n", + "request_payload = {\n", + " \"aoi\": aoi,\n", + " \"spatial_join_method\": \"centroid\",\n", + " \"fields\": available_fields # Use all available fields\n", + "}\n", + "\n", + "# Get Summary Data\n", + "response = requests.post(SUMMARY_ENDPOINT, json=request_payload)\n", + "if response.status_code != 200:\n", + " raise Exception(f\"Failed to get summary: {response.text}\")\n", + "\n", + "summary_data = response.json()\n", + "print(\"Summary Data:\", summary_data)\n", + "\n", + "# Convert Summary Data to DataFrame\n", + "summary_df = pd.DataFrame(summary_data)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
hex_id__index_level_0__ogc_fidsum_pop_f_0_2020sum_pop_f_10_2020sum_pop_f_15_2020sum_pop_f_1_2020sum_pop_f_20_2020sum_pop_f_25_2020sum_pop_f_30_2020...sum_pop_m_40_2020sum_pop_m_45_2020sum_pop_m_50_2020sum_pop_m_55_2020sum_pop_m_5_2020sum_pop_m_60_2020sum_pop_m_65_2020sum_pop_m_70_2020sum_pop_m_75_2020sum_pop_m_80_2020
0862a1008fffffff2436230113355.96850612371.39550815563.88964812494.43359430224.13085942427.28125034711.562500...21449.72656218393.36718817531.78906218519.33398412790.00390617991.04687515532.09277312730.6660168303.8662119250.683594
1862a100d7ffffff2436238193169.98120111957.44140614855.18554711801.99316427791.64453138212.46875031441.304688...19612.83593816889.30664116264.78320317167.78906212373.45507816715.09570314265.35156211801.8378917603.2309578574.225586
2862a100dfffffff2436239205147.33300821324.52343822045.63671919163.77148427736.29101635799.52343833238.703125...20631.27539118518.20703118673.70898419300.26953121773.69531218752.81640614246.96679712312.9628917566.3466808843.825195
3862a10707ffffff2436390491031.5867923744.3239753917.2214363840.6474615425.8300788483.6679698123.698242...5050.2407234325.6865234091.5224614049.4477543862.1474613625.9748542958.1362302380.1950681527.4406741581.647461
4862a1070fffffff243639150285.2675781234.0334471272.8674321062.0651861501.9545901971.1877441970.330566...1456.8050541328.0275881317.7327881337.0249021246.0129391250.8129881011.778931772.699219493.368988513.383545
5862a10727ffffff2436394531815.3031016547.2670907142.4624026758.46142610860.01660216565.37500015229.623047...9380.0488288014.2597667586.5253917627.6093756769.3417976963.2094735769.1958014668.7470703006.2675783161.916504
6862a1072fffffff2436395541863.8820807031.2617198313.1562506939.32373014435.01171920009.31250016851.132812...10254.2363288837.0117198528.3359388954.8183597267.7524418682.8642587253.9101566027.0146483859.2119144310.084961
7862a10757ffffff243639958834.6804813883.0231933978.9433593107.5561524433.3823245126.5019535110.866699...3944.7519533706.3725593792.6491703921.2607423899.2905273796.0744632990.9345702328.7233891461.9290771593.953613
8862a10767ffffff2436401603148.86889613023.14843813454.26953111723.39257816926.83203121846.34375020248.339844...12466.59863311185.37109411283.83789111677.32031213300.94531211366.2050788611.4062507453.0439454575.9609385342.289551
9862a10777ffffff2436403623401.08960014066.28710914531.94140612662.42187518282.65039123596.21093821870.207031...13465.15918012081.30664112187.66015612612.66015614366.33593812276.6250009301.1689458050.0249024942.4902345770.201172
\n", + "

10 rows × 39 columns

\n", + "
" + ], + "text/plain": [ + " hex_id __index_level_0__ ogc_fid sum_pop_f_0_2020 \\\n", + "0 862a1008fffffff 2436230 11 3355.968506 \n", + "1 862a100d7ffffff 2436238 19 3169.981201 \n", + "2 862a100dfffffff 2436239 20 5147.333008 \n", + "3 862a10707ffffff 2436390 49 1031.586792 \n", + "4 862a1070fffffff 2436391 50 285.267578 \n", + "5 862a10727ffffff 2436394 53 1815.303101 \n", + "6 862a1072fffffff 2436395 54 1863.882080 \n", + "7 862a10757ffffff 2436399 58 834.680481 \n", + "8 862a10767ffffff 2436401 60 3148.868896 \n", + "9 862a10777ffffff 2436403 62 3401.089600 \n", + "\n", + " sum_pop_f_10_2020 sum_pop_f_15_2020 sum_pop_f_1_2020 sum_pop_f_20_2020 \\\n", + "0 12371.395508 15563.889648 12494.433594 30224.130859 \n", + "1 11957.441406 14855.185547 11801.993164 27791.644531 \n", + "2 21324.523438 22045.636719 19163.771484 27736.291016 \n", + "3 3744.323975 3917.221436 3840.647461 5425.830078 \n", + "4 1234.033447 1272.867432 1062.065186 1501.954590 \n", + "5 6547.267090 7142.462402 6758.461426 10860.016602 \n", + "6 7031.261719 8313.156250 6939.323730 14435.011719 \n", + "7 3883.023193 3978.943359 3107.556152 4433.382324 \n", + "8 13023.148438 13454.269531 11723.392578 16926.832031 \n", + "9 14066.287109 14531.941406 12662.421875 18282.650391 \n", + "\n", + " sum_pop_f_25_2020 sum_pop_f_30_2020 ... sum_pop_m_40_2020 \\\n", + "0 42427.281250 34711.562500 ... 21449.726562 \n", + "1 38212.468750 31441.304688 ... 19612.835938 \n", + "2 35799.523438 33238.703125 ... 20631.275391 \n", + "3 8483.667969 8123.698242 ... 5050.240723 \n", + "4 1971.187744 1970.330566 ... 1456.805054 \n", + "5 16565.375000 15229.623047 ... 9380.048828 \n", + "6 20009.312500 16851.132812 ... 10254.236328 \n", + "7 5126.501953 5110.866699 ... 3944.751953 \n", + "8 21846.343750 20248.339844 ... 12466.598633 \n", + "9 23596.210938 21870.207031 ... 13465.159180 \n", + "\n", + " sum_pop_m_45_2020 sum_pop_m_50_2020 sum_pop_m_55_2020 sum_pop_m_5_2020 \\\n", + "0 18393.367188 17531.789062 18519.333984 12790.003906 \n", + "1 16889.306641 16264.783203 17167.789062 12373.455078 \n", + "2 18518.207031 18673.708984 19300.269531 21773.695312 \n", + "3 4325.686523 4091.522461 4049.447754 3862.147461 \n", + "4 1328.027588 1317.732788 1337.024902 1246.012939 \n", + "5 8014.259766 7586.525391 7627.609375 6769.341797 \n", + "6 8837.011719 8528.335938 8954.818359 7267.752441 \n", + "7 3706.372559 3792.649170 3921.260742 3899.290527 \n", + "8 11185.371094 11283.837891 11677.320312 13300.945312 \n", + "9 12081.306641 12187.660156 12612.660156 14366.335938 \n", + "\n", + " sum_pop_m_60_2020 sum_pop_m_65_2020 sum_pop_m_70_2020 sum_pop_m_75_2020 \\\n", + "0 17991.046875 15532.092773 12730.666016 8303.866211 \n", + "1 16715.095703 14265.351562 11801.837891 7603.230957 \n", + "2 18752.816406 14246.966797 12312.962891 7566.346680 \n", + "3 3625.974854 2958.136230 2380.195068 1527.440674 \n", + "4 1250.812988 1011.778931 772.699219 493.368988 \n", + "5 6963.209473 5769.195801 4668.747070 3006.267578 \n", + "6 8682.864258 7253.910156 6027.014648 3859.211914 \n", + "7 3796.074463 2990.934570 2328.723389 1461.929077 \n", + "8 11366.205078 8611.406250 7453.043945 4575.960938 \n", + "9 12276.625000 9301.168945 8050.024902 4942.490234 \n", + "\n", + " sum_pop_m_80_2020 \n", + "0 9250.683594 \n", + "1 8574.225586 \n", + "2 8843.825195 \n", + "3 1581.647461 \n", + "4 513.383545 \n", + "5 3161.916504 \n", + "6 4310.084961 \n", + "7 1593.953613 \n", + "8 5342.289551 \n", + "9 5770.201172 \n", + "\n", + "[10 rows x 39 columns]" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "summary_df" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "h3_id: 862a1008fffffff, boundary: ((-73.9609635556213, 40.829061732419916), (-74.00569135138367, 40.82168093780192), (-74.01944838354662, 40.79043914023696), (-73.98852232840895, 40.766594382212254), (-73.94384490497663, 40.77396440203852), (-73.93004320296495, 40.805189944379514), (-73.9609635556213, 40.829061732419916))\n", + "h3_id: 862a100d7ffffff, boundary: ((-73.94384490497663, 40.77396440203852), (-73.98852232840895, 40.766594382212254), (-74.0022744646159, 40.73537602621502), (-73.97139376102862, 40.71154393543933), (-73.92676660481357, 40.71890320501306), (-73.91296992347031, 40.75010530534533), (-73.94384490497663, 40.77396440203852))\n", + "h3_id: 862a100dfffffff, boundary: ((-73.92676660481357, 40.71890320501306), (-73.97139376102862, 40.71154393543933), (-73.98514098370819, 40.68034904926758), (-73.95430550947268, 40.656529678451555), (-73.90972851565844, 40.663878222244435), (-73.89593687206985, 40.69505685239637), (-73.92676660481357, 40.71890320501306))\n", + "h3_id: 862a10707ffffff, boundary: ((-74.07783966534221, 40.751803958414136), (-74.1224794808745, 40.74438358782839), (-74.1361375042681, 40.713156370029125), (-74.1052004095288, 40.68936564809726), (-74.06061071222366, 40.69677514034903), (-74.04690802968624, 40.727986222489115), (-74.07783966534221, 40.751803958414136))\n", + "h3_id: 862a1070fffffff, boundary: ((-74.06061071222366, 40.69677514034903), (-74.1052004095288, 40.68936564809726), (-74.11885375049164, 40.65816192704663), (-74.0879619670209, 40.63438382422961), (-74.04342228384493, 40.641782462872115), (-74.02972440815668, 40.67297004759546), (-74.06061071222366, 40.69677514034903))\n", + "h3_id: 862a10727ffffff, boundary: ((-74.01944838354662, 40.79043914023696), (-74.06413219384363, 40.783038509825), (-74.07783966534221, 40.751803958414136), (-74.04690802968624, 40.727986222489115), (-74.0022744646159, 40.73537602621502), (-73.98852232840895, 40.766594382212254), (-74.01944838354662, 40.79043914023696))\n", + "h3_id: 862a1072fffffff, boundary: ((-74.0022744646159, 40.73537602621502), (-74.04690802968624, 40.727986222489115), (-74.06061071222366, 40.69677514034903), (-74.02972440815668, 40.67297004759546), (-73.98514098370819, 40.68034904926758), (-73.97139376102862, 40.71154393543933), (-74.0022744646159, 40.73537602621502))\n", + "h3_id: 862a10757ffffff, boundary: ((-74.04342228384493, 40.641782462872115), (-74.0879619670209, 40.63438382422961), (-74.10161060735068, 40.603203627961975), (-74.07076401333117, 40.57943819693198), (-74.0262742404777, 40.586826006622616), (-74.01258118935834, 40.61799006598163), (-74.04342228384493, 40.641782462872115))\n", + "h3_id: 862a10767ffffff, boundary: ((-73.90972851565844, 40.663878222244435), (-73.95430550947268, 40.656529678451555), (-73.96804780122075, 40.62535829016475), (-73.93725743459962, 40.601551691753514), (-73.89273049866527, 40.608889534170004), (-73.87894390975266, 40.64004466616806), (-73.90972851565844, 40.663878222244435))\n", + "h3_id: 862a10777ffffff, boundary: ((-73.98514098370819, 40.68034904926758), (-74.02972440815668, 40.67297004759546), (-74.04342228384493, 40.641782462872115), (-74.01258118935834, 40.61799006598163), (-73.96804780122075, 40.62535829016475), (-73.95430550947268, 40.656529678451555), (-73.98514098370819, 40.68034904926758))\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/zacdez/Library/Caches/pypoetry/virtualenvs/notebooks-sNYx7QfP-py3.12/lib/python3.12/site-packages/lonboard/_geoarrow/ops/reproject.py:23: UserWarning: No CRS exists on data. If no data is shown on the map, double check that your CRS is WGS84.\n", + " warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b80af907501741a5a53bed80677e7eb4", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Map(basemap_style= List[str]: + """ + Generate H3 hexagon IDs for a given AOI GeoJSON object. + + Parameters: + aoi_geojson (Dict[str, Any]): The AOI GeoJSON object. + resolution (int): The H3 resolution. + spatial_join_method (str): The spatial join method to use. + + Returns: + List[str]: A list of H3 hexagon IDs. + """ + if spatial_join_method not in ["touches", "within", "centroid"]: + raise ValueError("Invalid spatial join method") + + # Convert GeoJSON to Shapely geometry + aoi_shape = shape(aoi_geojson) + + # Generate H3 hexagons covering the AOI + h3_ids = h3.polyfill(aoi_geojson, resolution, geo_json_conformant=True) + + # Filter hexagons based on spatial join method + # Touches method returns plain h3_ids + if spatial_join_method == "within": + h3_ids = [ + h3_id + for h3_id in h3_ids + if aoi_shape.contains(Polygon(h3.h3_to_geo_boundary(h3_id, geo_json=True))) + ] + elif spatial_join_method == "centroid": + h3_ids = [ + h3_id + for h3_id in h3_ids + if aoi_shape.contains( + Polygon(h3.h3_to_geo_boundary(h3_id, geo_json=True)).centroid + ) + ] + + return h3_ids diff --git a/space2stats_api/requirements.txt b/space2stats_api/requirements.txt new file mode 100644 index 0000000..1f46cef --- /dev/null +++ b/space2stats_api/requirements.txt @@ -0,0 +1,12 @@ +fastapi +uvicorn +pandas +python-dotenv +shapely +h3 +pytest +psycopg[binary] +httpx +geojson-pydantic +shapely +pydantic-settings>=2.0.0 \ No newline at end of file diff --git a/space2stats_api/tests/__init__.py b/space2stats_api/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/space2stats_api/tests/test_api.py b/space2stats_api/tests/test_api.py new file mode 100644 index 0000000..084a556 --- /dev/null +++ b/space2stats_api/tests/test_api.py @@ -0,0 +1,77 @@ +from fastapi.testclient import TestClient +import pytest +from unittest.mock import patch + +from app.main import app + + +client = TestClient(app) + + +def test_read_root(): + response = client.get("/") + assert response.status_code == 200 + assert response.json() == {"message": "Welcome to Space2Stats!"} + + +@patch("psycopg.connect") +def test_get_summary(mock_connect): + mock_cursor = mock_connect.return_value.cursor.return_value + mock_cursor.description = [("hex_id",), ("field1",), ("field2",)] + mock_cursor.fetchall.return_value = [("hex_1", 100, 200)] + + aoi = { + "type": "Feature", + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-74.1, 40.6], + [-73.9, 40.6], + [-73.9, 40.8], + [-74.1, 40.8], + [-74.1, 40.6], + ] + ], + }, + "properties": {}, + } + + request_payload = { + "aoi": aoi, + "spatial_join_method": "touches", + "fields": ["field1", "field2"], + } + + response = client.post("/summary", json=request_payload) + + assert response.status_code == 200 + response_json = response.json() + print(response_json) + assert isinstance(response_json, list) + + for summary in response_json: + assert "hex_id" in summary + for field in request_payload["fields"]: + assert field in summary + assert len(summary) == len(request_payload["fields"]) + 1 # +1 for the 'hex_id' + + +@patch("psycopg.connect") +def test_get_fields(mock_connect): + mock_cursor = mock_connect.return_value.cursor.return_value + mock_cursor.fetchall.return_value = [ + ("hex_id",), + ("field1",), + ("field2",), + ("field3",), + ] + + response = client.get("/fields") + + assert response.status_code == 200 + assert response.json() == ["field1", "field2", "field3"] + + +if __name__ == "__main__": + pytest.main() diff --git a/space2stats_api/tests/test_db_utils.py b/space2stats_api/tests/test_db_utils.py new file mode 100644 index 0000000..0047b05 --- /dev/null +++ b/space2stats_api/tests/test_db_utils.py @@ -0,0 +1,57 @@ +import unittest +from unittest.mock import patch, Mock +from app.utils.db_utils import get_summaries, get_available_fields + + +@patch("psycopg.connect") +def test_get_summaries(mock_connect): + mock_conn = Mock() + mock_cursor = Mock() + mock_connect.return_value = mock_conn + mock_conn.cursor.return_value = mock_cursor + + mock_cursor.description = [("hex_id",), ("field1",), ("field2",)] + mock_cursor.fetchall.return_value = [("hex_1", 100, 200)] + + fields = ["field1", "field2"] + h3_ids = ["hex_1"] + rows, colnames = get_summaries(fields, h3_ids) + + mock_connect.assert_called_once() + mock_cursor.execute.assert_called_once_with( + f""" + SELECT hex_id, {', '.join(fields)} + FROM space2stats + WHERE hex_id IN ('hex_1') + """ + ) + + assert rows == [("hex_1", 100, 200)] + assert colnames == ["hex_id", "field1", "field2"] + + +@patch("psycopg.connect") +def test_get_available_fields(mock_connect): + mock_conn = Mock() + mock_cursor = Mock() + mock_connect.return_value = mock_conn + mock_conn.cursor.return_value = mock_cursor + + mock_cursor.fetchall.return_value = [("field1",), ("field2",), ("field3",)] + + columns = get_available_fields() + + mock_connect.assert_called_once() + mock_cursor.execute.assert_called_once_with( + f""" + SELECT column_name + FROM information_schema.columns + WHERE table_name = 'space2stats' + """ + ) + + assert columns == ["field1", "field2", "field3"] + + +if __name__ == "__main__": + unittest.main() diff --git a/space2stats_api/tests/test_h3_utils.py b/space2stats_api/tests/test_h3_utils.py new file mode 100644 index 0000000..ce6e3ae --- /dev/null +++ b/space2stats_api/tests/test_h3_utils.py @@ -0,0 +1,41 @@ +import pytest +from shapely.geometry import Polygon, mapping +from app.utils.h3_utils import generate_h3_ids + +polygon_coords = [ + [-74.3, 40.5], + [-73.7, 40.5], + [-73.7, 40.9], + [-74.3, 40.9], + [-74.3, 40.5], +] +polygon = Polygon(polygon_coords) +aoi_geojson = mapping(polygon) +resolution = 6 + + +def test_generate_h3_ids_within(): + h3_ids = generate_h3_ids(aoi_geojson, resolution, "within") + print(f"Test 'within' - Generated H3 IDs: {h3_ids}") + assert len(h3_ids) > 0, "Expected at least one H3 ID" + + +def test_generate_h3_ids_touches(): + h3_ids = generate_h3_ids(aoi_geojson, resolution, "touches") + print(f"Test 'touches' - Generated H3 IDs: {h3_ids}") + assert len(h3_ids) > 0, "Expected at least one H3 ID" + + +def test_generate_h3_ids_centroid(): + h3_ids = generate_h3_ids(aoi_geojson, resolution, "centroid") + print(f"Test 'centroid' - Generated H3 IDs: {h3_ids}") + assert len(h3_ids) > 0, "Expected at least one H3 ID for centroid" + + +def test_generate_h3_ids_invalid_method(): + with pytest.raises(ValueError, match="Invalid spatial join method"): + generate_h3_ids(aoi_geojson, resolution, "invalid_method") + + +if __name__ == "__main__": + pytest.main()