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

Enhance Region Functionality with Geometry and Optimized Subregion Queries #39

Merged
merged 6 commits into from
Oct 25, 2023
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
92 changes: 89 additions & 3 deletions backend/src/controllers/regionController.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,57 @@
const { Region } = require('../models');
const { Model, DataTypes, QueryTypes } = require('sequelize');
const sequelize = require("../config/db");


exports.getGeometry = async (req, res) => {
const { regionId } = req.params;
const region = await Region.findOne({
where: { id: regionId }
});

if (!region) {
return res.status(404).json({ message: 'Region not found' });
}

let geometry = region.geom;

if (!geometry) {
const query = `
WITH RECURSIVE Subregions AS (
SELECT id, geom
FROM regions
WHERE id = :regionId
UNION ALL
SELECT r.id, r.geom
FROM regions r
INNER JOIN Subregions s ON r.parent_region_id = s.id
WHERE s.geom IS NULL
)
SELECT ST_AsGeoJSON(ST_Multi(ST_Union(geom))) as geometry
FROM Subregions
WHERE geom IS NOT NULL;
`;

const result = await sequelize.query(query, {
replacements: { regionId },
type: QueryTypes.SELECT
});

geometry = result.length > 0 ? result[0].geometry : null;

if (geometry) {
// Update the geometry in the database
await Region.update({ geom: geometry }, {
where: { id: regionId }
});
} else {
return res.status(404).json({ message: 'Geometry not found' });
}
}

return res.status(200).json({ geometry });
};


exports.getAncestors = async (req, res) => {
const { regionId } = req.params;
Expand Down Expand Up @@ -51,9 +104,36 @@ exports.getRegionById = async (req, res) => {
}
};


async function getAllSubregions(regionId) {
const query = `
WITH RECURSIVE Subregions AS (
SELECT id, parent_region_id as parentRegionId, name
FROM regions
WHERE parent_region_id = :regionId
UNION ALL
SELECT r.id, r.parent_region_id as parentRegionId, r.name
FROM regions r
INNER JOIN Subregions s ON r.parent_region_id = s.id
)
SELECT * FROM Subregions;
`;

const result = await sequelize.query(query, {
replacements: { regionId },
type: QueryTypes.SELECT,
mapToModel: true, // Required to map the result to Region instances
model: Region // The model to map to
});

return result;
}


// Retrieve subregions for a specific region
exports.getSubregions = async (req, res) => {
const { regionId } = req.params;
const { getAll } = req.query;

try {
// Check if the region exists
Expand All @@ -66,9 +146,15 @@ exports.getSubregions = async (req, res) => {
}

// Retrieve subregions
const subregions = await Region.findAll({
where: { parentRegionId: regionId }
});
let subregions;
// Check the getAll query parameter
if (getAll === 'true') {
subregions = await getAllSubregions(regionId);
} else {
subregions = await Region.findAll({
where: { parentRegionId: regionId }
});
}

if (subregions.length === 0) {
return res.status(202).json({ message: 'Region has no subregions' });
Expand Down
5 changes: 5 additions & 0 deletions backend/src/models/Region.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ Region.init({
type: DataTypes.INTEGER,
allowNull: true,
field: 'gadm_uid'
},
geom: {
type: DataTypes.GEOMETRY('MULTIPOLYGON', 4326),
allowNull: true,
field: 'geom'
}
}, {
sequelize,
Expand Down
21 changes: 18 additions & 3 deletions backend/src/routes/regionRoutes.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const express = require('express');
const regionController = require('../controllers/regionController');
const { check, validationResult } = require('express-validator');
const { check, query, validationResult } = require('express-validator');

const router = express.Router();

Expand All @@ -20,15 +20,16 @@ router.get('/:regionId',
);
router.get('/:regionId/subregions',
...[
check('regionId').isInt().withMessage('Region ID must be an integer')
check('regionId').isInt().withMessage('Region ID must be an integer'),
query('getAll').optional().isBoolean().withMessage('getAll must be a boolean')
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const errorMessages = errors.array().map(error => error.msg);
return res.status(400).json({errors: errorMessages});
}
await regionController.getSubregions(req, res);
await regionController.getSubregions(req, res, req.query.getAll);
}
);

Expand All @@ -46,4 +47,18 @@ router.get('/:regionId/ancestors',
}
);

router.get('/:regionId/geometry',
...[
check('regionId').isInt().withMessage('Region ID must be an integer')
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
const errorMessages = errors.array().map(error => error.msg);
return res.status(400).json({errors: errorMessages});
}
await regionController.getGeometry(req, res);
}
);

module.exports = router;
2 changes: 2 additions & 0 deletions deployment/init-db/init-regions-table.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ def find_next_non_empty_level(idx, feature, geo_levels):
print("Creating indexes...")
# Create indexes on the Region table
cur_pg.execute("CREATE INDEX IF NOT EXISTS parent_region_idx ON regions (parent_region_id)")
# Create a GiST index on the geometry column
cur_pg.execute("CREATE INDEX IF NOT EXISTS geom_idx ON regions USING GIST (geom)")
print("Done")

# Commit the changes and close the connections
Expand Down
Loading