From a767faeb8493bacfa2504db6cb56d2d429c0836c Mon Sep 17 00:00:00 2001 From: vitaligi <54726763+vitaligi@users.noreply.github.com> Date: Sun, 10 Aug 2025 14:20:41 +0300 Subject: [PATCH] feat!: support spatial and non-spatial aggregation of polygon parts --- src/schemas/core/aggregation.schema.ts | 84 ++++++++++++++++++-------- src/types/core/metadata.ts | 12 +++- src/utils/typeUtils.ts | 4 ++ 3 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/schemas/core/aggregation.schema.ts b/src/schemas/core/aggregation.schema.ts index 60ca1d5..2401be1 100644 --- a/src/schemas/core/aggregation.schema.ts +++ b/src/schemas/core/aggregation.schema.ts @@ -1,8 +1,9 @@ -import z from 'zod'; +import z, { type ZodObject, type ZodType, type ZodTypeAny } from 'zod'; import { CORE_VALIDATIONS, INGESTION_VALIDATIONS } from '../../constants'; +import type { SelectivePartial } from '../../utils/typeUtils'; import { featureSchema, multiPolygonSchema, polygonSchema } from './geo.schema'; -export const aggregationFeaturePropertiesSchema = z +const aggregationPropertiesSchema = z .object({ imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }), imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }), @@ -68,28 +69,59 @@ export const aggregationFeaturePropertiesSchema = z ) .min(1, { message: 'Sensors should have an array length of at least 1' }), }) - .strict() - .refine( - (aggregationLayerMetadata) => - aggregationLayerMetadata.imagingTimeBeginUTC <= aggregationLayerMetadata.imagingTimeEndUTC && - aggregationLayerMetadata.imagingTimeEndUTC <= new Date(), - { - message: 'Imaging time begin UTC should be less than or equal to imaging time end UTC and both less than or equal to current timestamp', - } - ) - .refine((aggregationLayerMetadata) => aggregationLayerMetadata.maxHorizontalAccuracyCE90 <= aggregationLayerMetadata.minHorizontalAccuracyCE90, { - message: 'Max horizontal accuracy CE90 should be less than or equal to min horizontal accuracy CE90', - }) - .refine((aggregationLayerMetadata) => aggregationLayerMetadata.maxResolutionDeg <= aggregationLayerMetadata.minResolutionDeg, { - message: 'Max resolution degree should be less than or equal to min resolution degree', - }) - .refine((aggregationLayerMetadata) => aggregationLayerMetadata.maxResolutionMeter <= aggregationLayerMetadata.minResolutionMeter, { - message: 'Max resolution meter should be less than or equal to min resolution meter', - }) - .describe('aggregationLayerMetadataSchema') - .describe('aggregationFeaturePropertiesSchema'); + .strict(); + +type SpatialAggregationProperties = 'productBoundingBox' | 'sensors'; +type AggregationProperties = SelectivePartial, SpatialAggregationProperties>; +type AggregationPropertiesZodSchemas = SelectivePartial<(typeof aggregationPropertiesSchema)['shape'], SpatialAggregationProperties>; + +const aggregationFeaturePropertiesSchemaBuilder = ( + input: ZodObject +): ZodType => { + return input + .refine( + (aggregationLayerMetadata) => + aggregationLayerMetadata.imagingTimeBeginUTC <= aggregationLayerMetadata.imagingTimeEndUTC && + aggregationLayerMetadata.imagingTimeEndUTC <= new Date(), + { + message: 'Imaging time begin UTC should be less than or equal to imaging time end UTC and both less than or equal to current timestamp', + } + ) + .refine((aggregationLayerMetadata) => aggregationLayerMetadata.maxHorizontalAccuracyCE90 <= aggregationLayerMetadata.minHorizontalAccuracyCE90, { + message: 'Max horizontal accuracy CE90 should be less than or equal to min horizontal accuracy CE90', + }) + .refine((aggregationLayerMetadata) => aggregationLayerMetadata.maxResolutionDeg <= aggregationLayerMetadata.minResolutionDeg, { + message: 'Max resolution degree should be less than or equal to min resolution degree', + }) + .refine( + (aggregationLayerMetadata): aggregationLayerMetadata is T => + aggregationLayerMetadata.maxResolutionMeter <= aggregationLayerMetadata.minResolutionMeter, + { + message: 'Max resolution meter should be less than or equal to min resolution meter', + } + ); +}; + +export const aggregationSpatialFeaturePropertiesSchema = aggregationFeaturePropertiesSchemaBuilder(aggregationPropertiesSchema).describe( + 'aggregationSpatialFeaturePropertiesSchema' +); + +export const aggregationNonSpatialFeaturePropertiesSchema = aggregationFeaturePropertiesSchemaBuilder( + aggregationPropertiesSchema.omit({ productBoundingBox: true }) +).describe('aggregationNonSpatialFeaturePropertiesSchema'); + +export const aggregationSpatialFeatureSchema = featureSchema( + polygonSchema.or(multiPolygonSchema), + aggregationSpatialFeaturePropertiesSchema +).describe('aggregationSpatialFeatureSchema'); + +export const aggregationNonSpatialFeatureSchema = featureSchema(z.null(), aggregationNonSpatialFeaturePropertiesSchema).describe( + 'aggregationNonSpatialFeatureSchema' +); + +export const aggregationEmptyFeatureSchema = featureSchema(z.null(), z.null()).describe('aggregationEmptyFeatureSchema'); -export const aggregationFeatureSchema = featureSchema( - polygonSchema.or(multiPolygonSchema).or(z.null()), - aggregationFeaturePropertiesSchema.or(z.null()) -).describe('aggregationFeatureSchema'); +export const aggregationFeatureSchema = aggregationSpatialFeatureSchema + .or(aggregationNonSpatialFeatureSchema) + .or(aggregationEmptyFeatureSchema) + .describe('aggregationFeatureSchema'); diff --git a/src/types/core/metadata.ts b/src/types/core/metadata.ts index e3f2054..122ccc1 100644 --- a/src/types/core/metadata.ts +++ b/src/types/core/metadata.ts @@ -1,7 +1,12 @@ -import z from 'zod'; import { IMetadataCommonModel, RecordStatus, TilesMimeFormat } from '@map-colonies/types'; +import z from 'zod'; import { TileOutputFormat, Transparency } from '../../constants/core'; -import { aggregationFeatureSchema } from '../../schemas'; +import { + aggregationEmptyFeatureSchema, + aggregationFeatureSchema, + aggregationNonSpatialFeatureSchema, + aggregationSpatialFeatureSchema, +} from '../../schemas'; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export type RasterLayerMetadata = { @@ -32,4 +37,7 @@ export type RasterLayerMetadata = { productStatus?: RecordStatus; } & IMetadataCommonModel; +export type AggregationSpatialFeature = z.infer; +export type AggregationNonSpatialFeature = z.infer; +export type AggregationEmptyFeature = z.infer; export type AggregationFeature = z.infer; diff --git a/src/utils/typeUtils.ts b/src/utils/typeUtils.ts index 8a66443..8eb63a5 100644 --- a/src/utils/typeUtils.ts +++ b/src/utils/typeUtils.ts @@ -5,3 +5,7 @@ export function pickEnum(e }); return picked; } + +export type SelectivePartial, U extends keyof T> = Omit & { + [K in U]?: T[K]; +};