/
Tech doc for Hub Allocation


Problem: searching whether a given point is contained within a partition using complete poygen( columns which is of type geometry made by converting geoJson data to WKB)

Solution: Implementing indexing on a new Column bounds which will be of data type geometry by converting { lat_min ,  lat_max ,  lng_min ,  lng_max } for a partition.

Faster initial filtering: Can quickly eliminate partitions that don't intersect the query point's bounding box.
Reduced index size: Smaller than an index on the full polygon.

1CREATE INDEX idx_partition_bounds ON partition USING gist(bounds); 2//using GiST as it is better than GiN index for spatial data.


Implementation:

→ Adding one more column as hub_id to store to which hub a partition belongs

FindAllPartitions :

We will be fetching all partitions which contains given point(lat,lng) using ST_Contains which will check whether boundary of a partition contains given point geom or not.

1Partition.findAllRequiredPartitions = async (organisationId, params = {}, options = {}) => { 2 assert(organisationId, 'organisationId is required'); 3 const { lat, lng } = params; 4 if (!lat || !lng) { 5 throw helper.wrongInputError('Invalid location details');; 6 } 7 const query = ` 8 SELECT * 9 FROM partition 10 WHERE 11 organisation_id = $1 12 AND ST_Contains(bounds,ST_SetSRID (ST_MakePoint ($2, $3), 4326)) 13 AND is_active = $4 14 `; 15 //ST_Contains checks whether first geometry contains second geometry 16 //ST_MakePoint : to create a point geometry. It takes coordinate values as input and returns a point object. 17 const queryResult = await helper.executeQueryAsync(Partition, query, [organisationId, lat, lng, true], options); 18 if (Array.isArray(queryResult) && queryResult.length) { 19 return queryResult; 20 } 21 return []; 22 };

Doubt: should we also include (ST_Intersects(bounds,ST_SetSRID (ST_MakePoint ($2, $3), 4326)) this in where condition


FindValidParition:

Fallback as of now is to throw err if no partition is found or if more than 1 is found.

1 Partition.findValidPartition = async (organisationId, params = {}, options = {}) => { 2 assert(organisationId, 'organisationId is required'); 3 const {lat, lng} = params; 4 const currentTx = options.transaction; 5 if (!lat || !lng) { 6 return {}; 7 } 8 const query = ` 9 SELECT * 10 FROM partition 11 WHERE 12 organisation_id = $1 13 AND ST_Contains(bounds,ST_SetSRID (ST_MakePoint ($2, $3), 4326)) 14 AND is_active = $4 15 `; 16 const queryResult = await helper.executeQueryAsync(Partition, query, [organisationId, lat, lng, true], {transaction: currentTx}); 17 if (Array.isArray(queryResult) && queryResult.length === 1) { 18 return queryResult[0]; 19 } else { 20 return helper.wrongInputError('Invalid location details'); 21 } 22 };


UpdatePartition :

1 Partition.updatePartition = async (organisationId, params = {}, options = {}) => { 2 const currentTx = options.transaction; 3 const { 4 description, 5 color, 6 partition_id: partitionId, 7 polygon_data: polygonData = [], 8 partition_code: partitionCode, 9 tags: tags = [], 10 is_active: isActive, 11 bounds: bounds, 12 hub_id: hubId, 13 } = params; 14 assert(currentTx, 'currentTx is required'); 15 assert(organisationId, 'organisationId is required'); 16 assert(partitionId, 'partitionId is required'); 17 18 const partitionAttributesToUpdate = {}; 19 20 const requiredPartition = await Partition.findOne({ 21 where: { 22 id: partitionId, 23 } 24 }, {transaction: currentTx}); 25 26 if (!requiredPartition) { 27 throw helper.notFoundError('Partition not found'); 28 }; 29 if (helper.hasKey(params, 'polygon_data', { blankIsFalse: true })) { 30 if (Array.isArray(polygonData) && polygonData.length) { 31 const polygon = postgisHelper.geojsonToWkb(postgisHelper.createPolyGeoJson([polygonData])); 32 partitionAttributesToUpdate.polygon = polygon; 33 }; 34 }; 35 36 if (helper.hasKey(params, 'bounds', { blankIsFalse: true })) { 37 if (Array.isArray(bounds) && bounds.length) { 38 const bounds = postgisHelper.geojsonToWkb(postgisHelper.createPolyGeoJson([bounds])); 39 partitionAttributesToUpdate.bounds = bounds; 40 }; 41 }; 42 43 if (helper.hasKey(params, 'partition_code', { blankIsFalse: true })) { 44 partitionAttributesToUpdate.partition_code = helper.sanitizeStringCode(partitionCode); 45 }; 46 47 if (helper.hasKey(params, 'hub_id', { blankIsFalse: true })) { 48 partitionAttributesToUpdate.hubId = helper.sanitizeStringCode(hubId); 49 }; 50 51 if (helper.hasKey(params, 'description', { blankIsFalse: true })) { 52 partitionAttributesToUpdate.description = description; 53 }; 54 55 if (helper.hasKey(params, 'tags', { blankIsFalse: true }) && Array.isArray(tags)) { 56 partitionAttributesToUpdate.tags = tags; 57 }; 58 59 if (helper.hasKey(params, 'is_active', { blankIsFalse: true })) { 60 partitionAttributesToUpdate.is_active = helper.parseBoolean(isActive); 61 }; 62 63 if (helper.hasKey(params, 'color', { blankIsFalse: true })) { 64 partitionAttributesToUpdate.color = color; 65 }; 66 67 await requiredPartition.updateAttributes(partitionAttributesToUpdate, { transaction: currentTx }); 68 };


CreatePartition :

1Partition.createPartition = async (organisationId, params = {}, options = {}) => { 2 3 const currentTx = options.transaction; 4 const { 5 description, 6 color, 7 polygon_data: polygonData, 8 tags: tags = [], 9 hub_id: hubId 10 } = params; 11 assert(currentTx, 'currentTx is required'); 12 assert(organisationId, 'organisationId is required'); 13 assert(params.partition_code, 'partitionCode is required'); 14 assert(params.hub_id, 'hubId is required'); 15 assert(Array.isArray(tags), 'tags should be array'); 16 17 if (!(Array.isArray(polygonData) && polygonData.length)) { 18 throw helper.wrongInputError('partition data should be array'); 19 }; 20 21 if (!(Array.isArray(bounds) && bounds.length)) { 22 throw helper.wrongInputError('bounds data should be array'); 23 }; 24 25 const partitionCode = helper.sanitizeStringCode(params.partition_code); 26 const existingPartition = await extendedModels.Partition.findOne({ 27 where: { 28 partition_code: partitionCode 29 }, 30 fields: { id: true } 31 }, { transaction: currentTx }); 32 33 if (existingPartition) { 34 throw helper.wrongInputError('', { 35 message: 'Partition code already exists', 36 reason: 'PARTITION_ALREADY_EXISTS' 37 }); 38 }; 39 if (helper.hasKey(params, 'hub_id', { blankIsFalse: true })) { 40 partitionAttributesToUpdate.hubId = helper.sanitizeStringCode(hubId); 41 }; 42 const polygon = postgisHelper.geojsonToWkb(postgisHelper.createPolyGeoJson([polygonData])); 43 const bounds = postgisHelper.geojsonToWkb(postgisHelper.createPolyGeoJson([bounds])); 44 await Partition.create({ 45 organisation_id: organisationId, 46 partition_code: partitionCode, 47 description: description, 48 tags: tags, 49 polygon: polygon, 50 color: color, 51 bounds: bounds, 52 hub_id: hubId 53 }, { transaction: currentTx }); 54 };


For hub Allocation using partition based method, a new IDB key will be used partition_based_hub_allocation while creating softadata and calling fn getConsignmentOriginHub

1else if(orgConfig.partition_based_hub_allocation) { 2 const lat = get(params, 'origin_address.lat', null); 3 const lng = get(params, 'origin_address.lng', null); 4 if (!lat || !lng){ 5 throw helper.wrongInputError('', { 6 message: 'latitude and latitude should be present for partition based hub allocation' 7 }); 8 } 9 const partition = await extendedModels.Partition.findValidPartition( organisationId, 10 { 11 lat: inputParams.lat, 12 lng: inputParams.lng, 13 }, options); 14 if( partition && partition.hub_id){ 15 hubId = partition.hub_id; 16 } 17 }


Similarly for destination hub allocation following logic will be added in getMultipleConsignmentDestinationHub fn:

1 else if (orgConfig.partition_based_hub_allocation) { 2 const lat = get(consignment, 'destination_address.lat', null); 3 const lng = get(consignment, 'destination_address.lng', null); 4 let plannedDestinationHubId = null, plannedDestinationHubCode = null, plannedDestinationHubActive = null; 5 if (!lat || !lng){ 6 throw helper.wrongInputError('', { 7 message: `${consignment.reference_number}:latitude and latitude should be present for partition based hub allocation` 8 }); 9 } 10 const partition = await extendedModels.Partition.findValidPartition( organisationId, 11 { 12 lat, 13 lng, 14 }, options); 15 16 17 if (partition && partition.hub_id) { 18 const requiredHub = await extendedModels.Hub.findOne({ 19 where: { 20 id: hubId 21 } 22 }); 23 if (!requiredHub) { 24 throw helper.wrongInputError('Hub not found!'); 25 } 26 27 plannedDestinationHubId = requiredHub.id; 28 plannedDestinationHubCode = requiredHub.code; 29 plannedDestinationHubActive = requiredHub.is_active; 30 } 31 32 toRet[consignment.reference_number] = { 33 plannedDestinationHubId, plannedDestinationHubCode, plannedDestinationHubActive 34 }; 35 } 36