From ac056840553869781169ce9eac463af3d58c7640 Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Tue, 1 Oct 2024 13:58:48 +0200 Subject: [PATCH 1/8] Add failing tests --- tests/geometry/geometry_collection.test.ts | 6 ++++ tests/geometry/line_string.test.ts | 3 ++ tests/geometry/multi_line_string.test.ts | 3 ++ tests/geometry/multi_point.test.ts | 3 ++ tests/geometry/multi_polygon.test.ts | 3 ++ tests/geometry/point.test.ts | 3 ++ tests/geometry/polygon.test.ts | 3 ++ tests/position.test.ts | 42 ++++++++++++++++++++++ 8 files changed, 66 insertions(+) diff --git a/tests/geometry/geometry_collection.test.ts b/tests/geometry/geometry_collection.test.ts index 60e6772..fbd208a 100644 --- a/tests/geometry/geometry_collection.test.ts +++ b/tests/geometry/geometry_collection.test.ts @@ -58,6 +58,12 @@ describe("GeoJSONGeometryCollection", () => { passGeoJSONGeometryCollectionTest({ type: "GeometryCollection", geometries: [] }); }); + it("does not allow a geometry collection with a 1D geometry", () => { + failGeoJSONGeometryCollectionTest({ + type: "GeometryCollection", + geometries: [{ type: "Point", coordinates: [0.0] }], + }); + }); it("does not allow a geometry collection without geometries key", () => { failGeoJSONGeometryCollectionTest({ type: "GeometryCollection" }); }); diff --git a/tests/geometry/line_string.test.ts b/tests/geometry/line_string.test.ts index 800414f..81e563b 100644 --- a/tests/geometry/line_string.test.ts +++ b/tests/geometry/line_string.test.ts @@ -37,6 +37,9 @@ describe("GeoJSONLineString", () => { passGeoJSONLineStringTest(geoJsonLineStringWithExtraKeys); }); + it("does not allow a 1D line string", () => { + failGeoJSONLineStringTest({ type: "LineString", coordinates: [[0.0], [1.0]] }); + }); it("does not allow a line string with empty coordinates", () => { failGeoJSONLineStringTest({ type: "LineString", coordinates: [] }); }); diff --git a/tests/geometry/multi_line_string.test.ts b/tests/geometry/multi_line_string.test.ts index a99d811..314b58c 100644 --- a/tests/geometry/multi_line_string.test.ts +++ b/tests/geometry/multi_line_string.test.ts @@ -45,6 +45,9 @@ describe("GeoJSONMultiLineString", () => { }); }); + it("does not allow a 1D multi-line string", () => { + failGeoJSONMultiLineStringTest({ type: "MultiLineString", coordinates: [[[0.0], [1.0]]] }); + }); it("does not allow a multi-line string with empty coordinates", () => { failGeoJSONMultiLineStringTest({ type: "MultiLineString", coordinates: [] }); }); diff --git a/tests/geometry/multi_point.test.ts b/tests/geometry/multi_point.test.ts index 51f31ca..1adbf9b 100644 --- a/tests/geometry/multi_point.test.ts +++ b/tests/geometry/multi_point.test.ts @@ -37,6 +37,9 @@ describe("GeoJSONMultiPoint", () => { }); }); + it("does not allow a 1D multi-point", () => { + failGeoJSONMultiPointTest({ type: "MultiPoint", coordinates: [[0.0], [1.0]] }); + }); it("does not allow a multi-point with empty coordinates", () => { failGeoJSONMultiPointTest({ type: "MultiPoint", coordinates: [] }); }); diff --git a/tests/geometry/multi_polygon.test.ts b/tests/geometry/multi_polygon.test.ts index eca6da8..1ab936c 100644 --- a/tests/geometry/multi_polygon.test.ts +++ b/tests/geometry/multi_polygon.test.ts @@ -45,6 +45,9 @@ describe("GeoJSONMultiPolygon", () => { }); }); + it("does not allow a 1D multi-polygon", () => { + failGeoJSONMultiPolygonTest({ type: "MultiPolygon", coordinates: [[[[0.0], [1.0], [0.0], [0.0]]]] }); + }); it("does not allow a multi-polygon with empty coordinates", () => { failGeoJSONMultiPolygonTest({ type: "MultiPolygon", coordinates: [] }); }); diff --git a/tests/geometry/point.test.ts b/tests/geometry/point.test.ts index ebec016..5ddab7e 100644 --- a/tests/geometry/point.test.ts +++ b/tests/geometry/point.test.ts @@ -43,6 +43,9 @@ describe("GeoJSONPoint", () => { passGeoJSONPointTest(geoJsonPointWithExtraKeys); }); + it("does not allow a 1D point", () => { + failGeoJSONPointTest({ type: "Point", coordinates: [1.0] }); + }); it("does not allow a point with empty coordinates", () => { failGeoJSONPointTest({ type: "Point", coordinates: [] }); }); diff --git a/tests/geometry/polygon.test.ts b/tests/geometry/polygon.test.ts index f626785..bc667ef 100644 --- a/tests/geometry/polygon.test.ts +++ b/tests/geometry/polygon.test.ts @@ -54,6 +54,9 @@ describe("GeoJSONPolygon", () => { }); }); + it("does not allow a 1D polygon", () => { + failGeoJSONPolygonTest({ type: "Polygon", coordinates: [[[0.0], [1.0], [0.0], [0.0]]] }); + }); it("does not allow a polygon without coordinates key", () => { failGeoJSONPolygonTest({ type: "Polygon" }); }); diff --git a/tests/position.test.ts b/tests/position.test.ts index ebf2796..ec6f452 100644 --- a/tests/position.test.ts +++ b/tests/position.test.ts @@ -23,4 +23,46 @@ describe("GeoJSONPosition", () => { it("does not allow empty positions", () => { expect(() => GeoJSONPositionSchema.parse([])).toThrow(ZodError); }); + + describe("inference", () => { + function testPositionType0D(_position: []): void {} + function testPositionType1D(_position: [number]): void {} + function testPositionType2D(_position: [number, number]): void {} + function testPositionType3D(_position: [number, number, number]): void {} + function testPositionType7D(_position: [number, number, number, number, number, number, number]): void {} + + it("should correctly infer 2D position", () => { + const position: GeoJSONPosition = [1, 1]; + + testPositionType2D(position); + }); + + it("should correctly infer 3D position", () => { + const position: GeoJSONPosition = [1, 1, 1]; + + testPositionType3D(position); + }); + + it("should correctly infer 7D position", () => { + const position: GeoJSONPosition = [1, 1, 1, 1, 1, 1, 1]; + + testPositionType7D(position); + }); + + it("should not infer 1D position", () => { + // @ts-expect-error -- This should fail + const position: GeoJSONPosition = [1]; + + // @ts-expect-error -- This should fail + testPositionType1D(position); + }); + + it("should not infer 0D position", () => { + // @ts-expect-error -- This should fail + const position: GeoJSONPosition = []; + + // @ts-expect-error -- This should fail + testPositionType0D(position); + }); + }); }); From 92e90ec915c4a2da8a12e12f5b6a644537afa944 Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Tue, 1 Oct 2024 14:06:13 +0200 Subject: [PATCH 2/8] Make GeoJSONPoints generic --- src/bbox.ts | 5 ++- src/geometry/point.ts | 41 +++++++++++----------- src/geometry/validation/bbox.ts | 32 +++++++++--------- src/position.ts | 14 ++++++-- tests/bbox.test.ts | 60 ++++++++++++++++++++++++++++++++- tests/position.test.ts | 42 ----------------------- 6 files changed, 110 insertions(+), 84 deletions(-) diff --git a/src/bbox.ts b/src/bbox.ts index 0b10b6d..6b765d4 100644 --- a/src/bbox.ts +++ b/src/bbox.ts @@ -1,8 +1,7 @@ import { z } from "zod"; -import { MIN_POSITION } from "./position"; export const GeoJSONBBoxSchema = z - .array(z.number()) - .min(MIN_POSITION * 2) + .tuple([z.number(), z.number(), z.number(), z.number()]) + .rest(z.number()) .refine((bbox) => bbox.length % 2 === 0, "Bounding box must have an even number of elements"); export type GeoJSONBbox = z.infer; diff --git a/src/geometry/point.ts b/src/geometry/point.ts index a68f321..671edd4 100644 --- a/src/geometry/point.ts +++ b/src/geometry/point.ts @@ -1,26 +1,29 @@ import { z } from "zod"; -import { GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; import { INVALID_BBOX_ISSUE, validBboxForPosition } from "./validation/bbox"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONPointSchema = GeoJSONBaseSchema.extend({ - type: z.literal("Point"), - coordinates: GeoJSONPositionSchema, -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - } - // Skip remaining checks if coordinates empty - if (!val.coordinates.length) { - return; - } - - if (!validBboxForPosition(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - } - }); +export const GeoJSONPointSchemaGeneric =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("Point"), + coordinates: positionSchema, + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryKeys(val)) { + ctx.addIssue(INVALID_KEYS_ISSUE); + } + if (!validBboxForPosition(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + } + }); +export const GeoJSONPointSchema = GeoJSONPointSchemaGeneric(GeoJSONPositionSchema); export type GeoJSONPoint = z.infer; + +export const GeoJSON2DPointSchema = GeoJSONPointSchemaGeneric(GeoJSON2DPositionSchema); +export type GeoJSON2DPoint = z.infer; + +export const GeoJSON3DPointSchema = GeoJSONPointSchemaGeneric(GeoJSON3DPositionSchema); +export type GeoJSON3DPoint = z.infer; diff --git a/src/geometry/validation/bbox.ts b/src/geometry/validation/bbox.ts index fbc87c4..496e5c1 100644 --- a/src/geometry/validation/bbox.ts +++ b/src/geometry/validation/bbox.ts @@ -1,9 +1,9 @@ import { GeoJSONGeometry } from "../index"; -type BboxPositionOptions = { bbox?: number[]; coordinates: number[] }; -type BboxPositionListOptions = { bbox?: number[]; coordinates: number[][] }; -type BboxPositionGridOptions = { bbox?: number[]; coordinates: number[][][] }; -type BboxPositionGridListOptions = { bbox?: number[]; coordinates: number[][][][] }; +type BboxPositionOptions = { bbox?: number[]; coordinates?: number[] }; +type BboxPositionListOptions = { bbox?: number[]; coordinates?: number[][] }; +type BboxPositionGridOptions = { bbox?: number[]; coordinates?: number[][][] }; +type BboxPositionGridListOptions = { bbox?: number[]; coordinates?: number[][][][] }; /** * Checks if given bbox is valid for the given position. @@ -11,9 +11,9 @@ type BboxPositionGridListOptions = { bbox?: number[]; coordinates: number[][][][ * @param coordinates Contains the position */ export function validBboxForPosition({ bbox, coordinates }: BboxPositionOptions): boolean { - if (!bbox) { - return true; - } + if (bbox == null) return true; + if (coordinates == null) return false; + const dimension = coordinates.length; if (bbox.length !== dimension * 2) { return false; @@ -27,9 +27,9 @@ export function validBboxForPosition({ bbox, coordinates }: BboxPositionOptions) * @param coordinates Contains the list of positions */ export function validBboxForPositionList({ bbox, coordinates }: BboxPositionListOptions): boolean { - if (!bbox) { - return true; - } + if (bbox == null) return true; + if (coordinates == null) return false; + const dimension = coordinates[0].length; if (bbox.length !== dimension * 2) { return false; @@ -46,9 +46,9 @@ export function validBboxForPositionList({ bbox, coordinates }: BboxPositionList * @param coordinates Contains the grid of positions */ export function validBboxForPositionGrid({ bbox, coordinates }: BboxPositionGridOptions): boolean { - if (bbox == null) { - return true; - } + if (bbox == null) return true; + if (coordinates == null) return false; + const dimension = coordinates[0][0].length; if (bbox.length !== 2 * dimension) { return false; @@ -65,9 +65,9 @@ export function validBboxForPositionGrid({ bbox, coordinates }: BboxPositionGrid * @param coordinates Contains the grid of positions */ export function validBboxForPositionGridList({ bbox, coordinates }: BboxPositionGridListOptions): boolean { - if (bbox == null) { - return true; - } + if (bbox == null) return true; + if (coordinates == null) return false; + const dimension = coordinates[0][0][0].length; if (bbox.length !== 2 * dimension) { return false; diff --git a/src/position.ts b/src/position.ts index d69e5ab..b1afa8f 100644 --- a/src/position.ts +++ b/src/position.ts @@ -1,8 +1,16 @@ import { z } from "zod"; -export const MIN_POSITION = 2 as const; - // GeoJSON positions and coordinates (see 3.1.1) // Array: [longitude/easting, latitude/northing, altitude (optional), ...extra elements are unspecified and ambiguous] -export const GeoJSONPositionSchema = z.array(z.number()).min(MIN_POSITION); +export const GeoJSONPositionSchema = z.tuple([z.number(), z.number()]).rest(z.number()); export type GeoJSONPosition = z.infer; + +// Specific GeoJSON positions for 2 dimensions +// These are used to define the 2D geometries, features, and collections +export const GeoJSON2DPositionSchema = z.tuple([z.number(), z.number()]); +export type GeoJSON2DPosition = z.infer; + +// Specific GeoJSON positions for 3 dimensions +// These are used to define the 3D geometries, features, and collections +export const GeoJSON3DPositionSchema = z.tuple([z.number(), z.number(), z.number()]); +export type GeoJSON3DPosition = z.infer; diff --git a/tests/bbox.test.ts b/tests/bbox.test.ts index fd28b55..2b37c98 100644 --- a/tests/bbox.test.ts +++ b/tests/bbox.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "@jest/globals"; import { ZodError } from "zod"; import { bbox2D, bbox3D, bbox4D } from "../examples/bbox"; -import { GeoJSONBBoxSchema } from "../src"; +import { GeoJSONBBoxSchema, GeoJSONBbox } from "../src"; describe("GeoJSONBBox", () => { it("allows 2D bbox", () => { @@ -39,4 +39,62 @@ describe("GeoJSONBBox", () => { it("does not allow a badly formatted bbox", () => { expect(() => GeoJSONBBoxSchema.parse("bbox cannot be a string")).toThrow(ZodError); }); + + describe("inference", () => { + function testBboxType0D(_position: []): void {} + function testBboxType1D(_position: [number]): void {} + function testBboxType2D(_position: [number, number]): void {} + function testBboxType3D(_position: [number, number, number]): void {} + function testBboxTypeCorrect(_position: [number, number, number, number, ...number[]]): void {} + + it("should correctly infer 4D bbox", () => { + const bbox: GeoJSONBbox = [1, 1, 1, 1]; + + testBboxTypeCorrect(bbox); + }); + + it("should correctly infer 6D bbox", () => { + const bbox: GeoJSONBbox = [1, 1, 1, 1, 1, 1]; + + testBboxTypeCorrect(bbox); + }); + + it("should correctly infer 10D bbox", () => { + const bbox: GeoJSONBbox = [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; + + testBboxTypeCorrect(bbox); + }); + + it("should not infer 3D bbox", () => { + // @ts-expect-error -- This should fail + const bbox: GeoJSONBbox = [1, 1, 1]; + + // @ts-expect-error -- This should fail + testBboxType3D(bbox); + }); + + it("should not infer 2D bbox", () => { + // @ts-expect-error -- This should fail + const bbox: GeoJSONBbox = [1, 1]; + + // @ts-expect-error -- This should fail + testBboxType2D(bbox); + }); + + it("should not infer 1D bbox", () => { + // @ts-expect-error -- This should fail + const bbox: GeoJSONBbox = [1]; + + // @ts-expect-error -- This should fail + testBboxType1D(bbox); + }); + + it("should not infer 0D bbox", () => { + // @ts-expect-error -- This should fail + const bbox: GeoJSONBbox = []; + + // @ts-expect-error -- This should fail + testBboxType0D(bbox); + }); + }); }); diff --git a/tests/position.test.ts b/tests/position.test.ts index ec6f452..ebf2796 100644 --- a/tests/position.test.ts +++ b/tests/position.test.ts @@ -23,46 +23,4 @@ describe("GeoJSONPosition", () => { it("does not allow empty positions", () => { expect(() => GeoJSONPositionSchema.parse([])).toThrow(ZodError); }); - - describe("inference", () => { - function testPositionType0D(_position: []): void {} - function testPositionType1D(_position: [number]): void {} - function testPositionType2D(_position: [number, number]): void {} - function testPositionType3D(_position: [number, number, number]): void {} - function testPositionType7D(_position: [number, number, number, number, number, number, number]): void {} - - it("should correctly infer 2D position", () => { - const position: GeoJSONPosition = [1, 1]; - - testPositionType2D(position); - }); - - it("should correctly infer 3D position", () => { - const position: GeoJSONPosition = [1, 1, 1]; - - testPositionType3D(position); - }); - - it("should correctly infer 7D position", () => { - const position: GeoJSONPosition = [1, 1, 1, 1, 1, 1, 1]; - - testPositionType7D(position); - }); - - it("should not infer 1D position", () => { - // @ts-expect-error -- This should fail - const position: GeoJSONPosition = [1]; - - // @ts-expect-error -- This should fail - testPositionType1D(position); - }); - - it("should not infer 0D position", () => { - // @ts-expect-error -- This should fail - const position: GeoJSONPosition = []; - - // @ts-expect-error -- This should fail - testPositionType0D(position); - }); - }); }); From 833947a4a392a25d5028d2840ae40413df302449 Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Wed, 2 Oct 2024 21:56:22 +0200 Subject: [PATCH 3/8] Implement all geometries as generics + remove recursive geom collection --- src/geometry/_simple.ts | 28 +++++++----- src/geometry/geometry_collection.ts | 65 ++++++++++++++------------ src/geometry/index.ts | 16 ++++++- src/geometry/line_string.ts | 58 ++++++++++++----------- src/geometry/multi_line_string.ts | 57 +++++++++++++---------- src/geometry/multi_point.ts | 56 +++++++++++++---------- src/geometry/multi_polygon.ts | 71 ++++++++++++++++------------- src/geometry/point.ts | 8 ++-- src/geometry/polygon.ts | 68 ++++++++++++++------------- 9 files changed, 246 insertions(+), 181 deletions(-) diff --git a/src/geometry/_simple.ts b/src/geometry/_simple.ts index b14db12..88a12db 100644 --- a/src/geometry/_simple.ts +++ b/src/geometry/_simple.ts @@ -1,12 +1,18 @@ -import { GeoJSONLineStringSchema } from "./line_string"; -import { GeoJSONMultiLineStringSchema } from "./multi_line_string"; -import { GeoJSONMultiPointSchema } from "./multi_point"; -import { GeoJSONMultiPolygonSchema } from "./multi_polygon"; -import { GeoJSONPointSchema } from "./point"; -import { GeoJSONPolygonSchema } from "./polygon"; +import { z } from "zod"; +import { GeoJSONPosition } from "../position"; +import { GeoJSONLineStringGenericSchema } from "./line_string"; +import { GeoJSONMultiLineStringGenericSchema } from "./multi_line_string"; +import { GeoJSONMultiPointGenericSchema } from "./multi_point"; +import { GeoJSONMultiPolygonGenericSchema } from "./multi_polygon"; +import { GeoJSONPointGenericSchema } from "./point"; +import { GeoJSONPolygonGenericSchema } from "./polygon"; -export const _GeoJSONSimpleGeometrySchema = GeoJSONPointSchema.or(GeoJSONLineStringSchema) - .or(GeoJSONMultiPointSchema) - .or(GeoJSONPolygonSchema) - .or(GeoJSONMultiLineStringSchema) - .or(GeoJSONMultiPolygonSchema); +export const _GeoJSONSimpleGeometryGenericSchema =

(positionSchema: z.ZodSchema

) => + z.union([ + GeoJSONPointGenericSchema(positionSchema), + GeoJSONLineStringGenericSchema(positionSchema), + GeoJSONMultiPointGenericSchema(positionSchema), + GeoJSONPolygonGenericSchema(positionSchema), + GeoJSONMultiLineStringGenericSchema(positionSchema), + GeoJSONMultiPolygonGenericSchema(positionSchema), + ]); diff --git a/src/geometry/geometry_collection.ts b/src/geometry/geometry_collection.ts index e67ef06..61a80ce 100644 --- a/src/geometry/geometry_collection.ts +++ b/src/geometry/geometry_collection.ts @@ -1,8 +1,10 @@ -import { z, ZodType } from "zod"; +import { z } from "zod"; +import { _GeoJSONSimpleGeometryGenericSchema } from "./_simple"; import { bboxEquals, getBboxForGeometries, INVALID_BBOX_ISSUE } from "./validation/bbox"; import { getDimensionForGeometry } from "./validation/dimension"; import { GeoJSONBaseSchema } from "../base"; -import { GeoJSONGeometry, GeoJSONGeometrySchema } from "./index"; +import { GeoJSONGeometry } from "./index"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; const INVALID_GEOMETRY_COLLECTION_KEYS_ISSUE = { code: "custom" as const, @@ -24,7 +26,8 @@ function validGeometryCollectionKeys(collection: Record): boole ); } -function validGeometryCollectionDimension({ geometries }: { geometries: GeoJSONGeometry[] }): boolean { +function validGeometryCollectionDimension({ geometries }: { geometries?: GeoJSONGeometry[] }): boolean { + if (geometries == null) return false; let dimension = getDimensionForGeometry(geometries[0]); return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); } @@ -47,31 +50,37 @@ const _GeoJSONGeometryCollectionBaseSchema = GeoJSONBaseSchema.extend({ type: z.literal("GeometryCollection"), }); -export const GeoJSONGeometryCollectionSchema: ZodType = _GeoJSONGeometryCollectionBaseSchema - .extend({ - geometries: z.lazy(() => z.array(GeoJSONGeometrySchema)), - }) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryCollectionKeys(val)) { - ctx.addIssue(INVALID_GEOMETRY_COLLECTION_KEYS_ISSUE); - return; - } - if (!val.geometries.length) { - return; - } +export const GeoJSONGeometryCollectionGenericSchema =

(positionSchema: z.ZodSchema

) => + _GeoJSONGeometryCollectionBaseSchema + .extend({ + geometries: _GeoJSONSimpleGeometryGenericSchema(positionSchema).array(), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryCollectionKeys(val)) { + ctx.addIssue(INVALID_GEOMETRY_COLLECTION_KEYS_ISSUE); + return; + } + if (!val.geometries.length) { + return; + } - if (!validGeometryCollectionDimension(val)) { - ctx.addIssue(INVALID_GEOMETRY_COLLECTION_DIMENSION_ISSUE); - return; - } + if (!validGeometryCollectionDimension(val)) { + ctx.addIssue(INVALID_GEOMETRY_COLLECTION_DIMENSION_ISSUE); + return; + } - if (!validGeometryCollectionBbox(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); + if (!validGeometryCollectionBbox(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); -export type GeoJSONGeometryCollection = z.infer & { - geometries: GeoJSONGeometry[]; -}; +export const GeoJSONGeometryCollectionSchema = GeoJSONGeometryCollectionGenericSchema(GeoJSONPositionSchema); +export type GeoJSONGeometryCollection = z.infer; + +export const GeoJSON2DGeometryCollectionSchema = GeoJSONGeometryCollectionGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DGeometryCollection = z.infer; + +export const GeoJSON3DGeometryCollectionSchema = GeoJSONGeometryCollectionGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DGeometryCollection = z.infer; diff --git a/src/geometry/index.ts b/src/geometry/index.ts index 1f56c47..a47a10b 100644 --- a/src/geometry/index.ts +++ b/src/geometry/index.ts @@ -1,5 +1,6 @@ import { z } from "zod"; -import { _GeoJSONSimpleGeometrySchema } from "./_simple"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +import { _GeoJSONSimpleGeometryGenericSchema } from "./_simple"; import { GeoJSONGeometryCollectionSchema } from "./geometry_collection"; export * from "./geometry_collection"; @@ -10,6 +11,17 @@ export * from "./multi_polygon"; export * from "./point"; export * from "./polygon"; -export const GeoJSONGeometrySchema = _GeoJSONSimpleGeometrySchema.or(GeoJSONGeometryCollectionSchema); +export const GeoJSONGeometryGenericSchema =

(positionSchema: z.ZodSchema

) => + _GeoJSONSimpleGeometryGenericSchema(positionSchema).or(GeoJSONGeometryCollectionSchema); +export type GeoJSONGenericGeometry

= z.infer< + ReturnType> +>; +export const GeoJSONGeometrySchema = GeoJSONGeometryGenericSchema(GeoJSONPositionSchema); export type GeoJSONGeometry = z.infer; + +export const GeoJSON2DGeometrySchema = GeoJSONGeometryGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DGeometry = z.infer; + +export const GeoJSON3DGeometrySchema = GeoJSONGeometryGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DGeometry = z.infer; diff --git a/src/geometry/line_string.ts b/src/geometry/line_string.ts index 4f25a04..04b6f90 100644 --- a/src/geometry/line_string.ts +++ b/src/geometry/line_string.ts @@ -1,36 +1,42 @@ import { z } from "zod"; -import { GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionList } from "./validation/dimension"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; import { INVALID_BBOX_ISSUE, validBboxForPositionList } from "./validation/bbox"; import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONLineStringSchema = GeoJSONBaseSchema.extend({ - type: z.literal("LineString"), - coordinates: z.array(GeoJSONPositionSchema).min(2), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } +export const GeoJSONLineStringGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("LineString"), + coordinates: z.array(positionSchema).min(2), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryKeys(val)) { + ctx.addIssue(INVALID_KEYS_ISSUE); + return; + } - // Skip remaining checks if coordinates empty - if (!val.coordinates.length) { - return; - } + // Skip remaining checks if coordinates empty + if (!val.coordinates.length) { + return; + } - if (!validDimensionsForPositionList(val)) { - ctx.addIssue(INVALID_DIMENSIONS_ISSUE); - return; - } - if (!validBboxForPositionList(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); - -export const GeoJSONLineStringCoordinatesSchema = GeoJSONLineStringSchema.innerType().shape.coordinates; + if (!validDimensionsForPositionList(val)) { + ctx.addIssue(INVALID_DIMENSIONS_ISSUE); + return; + } + if (!validBboxForPositionList(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); +export const GeoJSONLineStringSchema = GeoJSONLineStringGenericSchema(GeoJSONPositionSchema); export type GeoJSONLineString = z.infer; + +export const GeoJSON2DLineStringSchema = GeoJSONLineStringGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DLineString = z.infer; + +export const GeoJSON3DLineStringSchema = GeoJSONLineStringGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DLineString = z.infer; diff --git a/src/geometry/multi_line_string.ts b/src/geometry/multi_line_string.ts index 26cf303..6d7ce83 100644 --- a/src/geometry/multi_line_string.ts +++ b/src/geometry/multi_line_string.ts @@ -1,33 +1,42 @@ import { z } from "zod"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; import { INVALID_BBOX_ISSUE, validBboxForPositionGrid } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionGrid } from "./validation/dimension"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONLineStringCoordinatesSchema } from "./line_string"; +import { GeoJSONLineStringGenericSchema } from "./line_string"; import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONMultiLineStringSchema = GeoJSONBaseSchema.extend({ - type: z.literal("MultiLineString"), - coordinates: z.array(GeoJSONLineStringCoordinatesSchema).min(1), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } - // Skip remaining checks if coordinates array is empty - if (!val.coordinates.length) { - return; - } +export const GeoJSONMultiLineStringGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("MultiLineString"), + coordinates: z.array(GeoJSONLineStringGenericSchema(positionSchema).innerType().shape.coordinates).min(1), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryKeys(val)) { + ctx.addIssue(INVALID_KEYS_ISSUE); + return; + } + // Skip remaining checks if coordinates array is empty + if (!val.coordinates.length) { + return; + } - if (!validDimensionsForPositionGrid(val)) { - ctx.addIssue(INVALID_DIMENSIONS_ISSUE); - return; - } - if (!validBboxForPositionGrid(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); + if (!validDimensionsForPositionGrid(val)) { + ctx.addIssue(INVALID_DIMENSIONS_ISSUE); + return; + } + if (!validBboxForPositionGrid(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); +export const GeoJSONMultiLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSONPositionSchema); export type GeoJSONMultiLineString = z.infer; + +export const GeoJSONMulti2DLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DMultiLineString = z.infer; + +export const GeoJSONMulti3DLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DMultiLineString = z.infer; diff --git a/src/geometry/multi_point.ts b/src/geometry/multi_point.ts index 8381cb6..38ea2db 100644 --- a/src/geometry/multi_point.ts +++ b/src/geometry/multi_point.ts @@ -1,33 +1,41 @@ import { z } from "zod"; -import { GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; import { INVALID_BBOX_ISSUE, validBboxForPositionList } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionList } from "./validation/dimension"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONMultiPointSchema = GeoJSONBaseSchema.extend({ - type: z.literal("MultiPoint"), - coordinates: z.array(GeoJSONPositionSchema).min(1), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } - // Skip remaining checks if coordinates empty - if (!val.coordinates.length) { - return; - } +export const GeoJSONMultiPointGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("MultiPoint"), + coordinates: z.array(positionSchema).min(1), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryKeys(val)) { + ctx.addIssue(INVALID_KEYS_ISSUE); + return; + } + // Skip remaining checks if coordinates empty + if (!val.coordinates.length) { + return; + } - if (!validDimensionsForPositionList(val)) { - ctx.addIssue(INVALID_DIMENSIONS_ISSUE); - return; - } - if (!validBboxForPositionList(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); + if (!validDimensionsForPositionList(val)) { + ctx.addIssue(INVALID_DIMENSIONS_ISSUE); + return; + } + if (!validBboxForPositionList(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); +export const GeoJSONMultiPointSchema = GeoJSONMultiPointGenericSchema(GeoJSONPositionSchema); export type GeoJSONMultiPoint = z.infer; + +export const GeoJSON2DMultiPointSchema = GeoJSONMultiPointGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DMultiPoint = z.infer; + +export const GeoJSON3DMultiPointSchema = GeoJSONMultiPointGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DMultiPoint = z.infer; diff --git a/src/geometry/multi_polygon.ts b/src/geometry/multi_polygon.ts index 30ba937..19edab3 100644 --- a/src/geometry/multi_polygon.ts +++ b/src/geometry/multi_polygon.ts @@ -1,8 +1,9 @@ import { z } from "zod"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; import { INVALID_BBOX_ISSUE, validBboxForPositionGridList } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionGridList } from "./validation/dimension"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; -import { GeoJSONPolygonCoordinatesSchema, validPolygonRings } from "./polygon"; +import { GeoJSONPolygonGenericSchema, validPolygonRings } from "./polygon"; import { GeoJSONBaseSchema } from "../base"; const INVALID_LINEAR_RING_MESSAGE = { @@ -14,35 +15,43 @@ function validMultiPolygonLinearRings({ coordinates }: { coordinates: number[][] return coordinates.every((polygon) => validPolygonRings({ coordinates: polygon })); } -export const GeoJSONMultiPolygonSchema = GeoJSONBaseSchema.extend({ - type: z.literal("MultiPolygon"), - coordinates: z.array(GeoJSONPolygonCoordinatesSchema).min(1), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } - // Skip remaining checks if coordinates array is empty - if (!val.coordinates.length) { - return; - } - - if (!validDimensionsForPositionGridList(val)) { - ctx.addIssue(INVALID_DIMENSIONS_ISSUE); - return; - } - - if (!validMultiPolygonLinearRings(val)) { - ctx.addIssue(INVALID_LINEAR_RING_MESSAGE); - return; - } - - if (!validBboxForPositionGridList(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); +export const GeoJSONMultiPolygonGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("MultiPolygon"), + coordinates: z.array(GeoJSONPolygonGenericSchema(positionSchema).innerType().shape.coordinates).min(1), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryKeys(val)) { + ctx.addIssue(INVALID_KEYS_ISSUE); + return; + } + // Skip remaining checks if coordinates array is empty + if (!val.coordinates.length) { + return; + } + if (!validDimensionsForPositionGridList(val)) { + ctx.addIssue(INVALID_DIMENSIONS_ISSUE); + return; + } + + if (!validMultiPolygonLinearRings(val)) { + ctx.addIssue(INVALID_LINEAR_RING_MESSAGE); + return; + } + + if (!validBboxForPositionGridList(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); + +export const GeoJSONMultiPolygonSchema = GeoJSONMultiPolygonGenericSchema(GeoJSONPositionSchema); export type GeoJSONMultiPolygon = z.infer; + +export const GeoJSON2DMultiPolygonSchema = GeoJSONMultiPolygonGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DMultiPolygon = z.infer; + +export const GeoJSON3DMultiPolygonSchema = GeoJSONMultiPolygonGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DMultiPolygon = z.infer; diff --git a/src/geometry/point.ts b/src/geometry/point.ts index 671edd4..72ed8ca 100644 --- a/src/geometry/point.ts +++ b/src/geometry/point.ts @@ -4,7 +4,7 @@ import { INVALID_BBOX_ISSUE, validBboxForPosition } from "./validation/bbox"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; import { GeoJSONBaseSchema } from "../base"; -export const GeoJSONPointSchemaGeneric =

(positionSchema: z.ZodSchema

) => +export const GeoJSONPointGenericSchema =

(positionSchema: z.ZodSchema

) => GeoJSONBaseSchema.extend({ type: z.literal("Point"), coordinates: positionSchema, @@ -19,11 +19,11 @@ export const GeoJSONPointSchemaGeneric =

(positionSch } }); -export const GeoJSONPointSchema = GeoJSONPointSchemaGeneric(GeoJSONPositionSchema); +export const GeoJSONPointSchema = GeoJSONPointGenericSchema(GeoJSONPositionSchema); export type GeoJSONPoint = z.infer; -export const GeoJSON2DPointSchema = GeoJSONPointSchemaGeneric(GeoJSON2DPositionSchema); +export const GeoJSON2DPointSchema = GeoJSONPointGenericSchema(GeoJSON2DPositionSchema); export type GeoJSON2DPoint = z.infer; -export const GeoJSON3DPointSchema = GeoJSONPointSchemaGeneric(GeoJSON3DPositionSchema); +export const GeoJSON3DPointSchema = GeoJSONPointGenericSchema(GeoJSON3DPositionSchema); export type GeoJSON3DPoint = z.infer; diff --git a/src/geometry/polygon.ts b/src/geometry/polygon.ts index abf6665..d6a6738 100644 --- a/src/geometry/polygon.ts +++ b/src/geometry/polygon.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import { GeoJSONPositionSchema } from "../position"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; import { INVALID_BBOX_ISSUE, validBboxForPositionGrid } from "./validation/bbox"; import { INVALID_DIMENSIONS_ISSUE, validDimensionsForPositionGrid } from "./validation/dimension"; import { INVALID_KEYS_ISSUE, validGeometryKeys } from "./validation/keys"; @@ -20,35 +20,41 @@ export function validPolygonRings({ coordinates: rings }: { coordinates: number[ return rings.every(validLinearRing); } -export const GeoJSONPolygonSchema = GeoJSONBaseSchema.extend({ - type: z.literal("Polygon"), - coordinates: z.array(z.array(GeoJSONPositionSchema).min(4)), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validGeometryKeys(val)) { - ctx.addIssue(INVALID_KEYS_ISSUE); - return; - } - // Skip remaining checks if coordinates array is empty - if (!val.coordinates.length) { - return; - } - - if (!validDimensionsForPositionGrid(val)) { - ctx.addIssue(INVALID_DIMENSIONS_ISSUE); - return; - } - if (!validPolygonRings(val)) { - ctx.addIssue(INVALID_LINEAR_RING_MESSAGE); - return; - } - if (!validBboxForPositionGrid(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); - -export const GeoJSONPolygonCoordinatesSchema = GeoJSONPolygonSchema.innerType().shape.coordinates; +export const GeoJSONPolygonGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("Polygon"), + coordinates: z.array(z.array(positionSchema).min(4)), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validGeometryKeys(val)) { + ctx.addIssue(INVALID_KEYS_ISSUE); + return; + } + // Skip remaining checks if coordinates array is empty + if (!val.coordinates.length) { + return; + } + if (!validDimensionsForPositionGrid(val)) { + ctx.addIssue(INVALID_DIMENSIONS_ISSUE); + return; + } + if (!validPolygonRings(val)) { + ctx.addIssue(INVALID_LINEAR_RING_MESSAGE); + return; + } + if (!validBboxForPositionGrid(val)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); + +export const GeoJSONPolygonSchema = GeoJSONPolygonGenericSchema(GeoJSONPositionSchema); export type GeoJSONPolygon = z.infer; + +export const GeoJSON2DPolygonSchema = GeoJSONPolygonGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DPolygon = z.infer; + +export const GeoJSON3DPolygonSchema = GeoJSONPolygonGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DPolygon = z.infer; From 4418ea32d22f51e53545440ddb1ac920eaf822c4 Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Wed, 2 Oct 2024 22:04:22 +0200 Subject: [PATCH 4/8] Fix type checks & removed nested geometry examples --- examples/geometry/geometry_collection.ts | 10 ---------- examples/geometry/multi_polygon.ts | 4 ++-- examples/geometry/polygon.ts | 4 ++-- src/geometry/geometry_collection.ts | 6 ++++-- tests/geometry/geometry_collection.test.ts | 8 -------- 5 files changed, 8 insertions(+), 24 deletions(-) diff --git a/examples/geometry/geometry_collection.ts b/examples/geometry/geometry_collection.ts index cab4277..729a580 100644 --- a/examples/geometry/geometry_collection.ts +++ b/examples/geometry/geometry_collection.ts @@ -21,11 +21,6 @@ export const multiGeoJsonGeometryCollection3D: GeoJSONGeometryCollection = { geometries: [geoJsonPoint3D, geoJsonLineString3D, singleGeoJsonMultiPolygon3D], }; -export const nestedGeoJsonGeometryCollection: GeoJSONGeometryCollection = { - type: "GeometryCollection", - geometries: [geoJsonPoint2D, multiGeoJsonGeometryCollection2D], -}; - export const singleGeoJsonGeometryCollection2DWithBbox: GeoJSONGeometryCollection = { ...singleGeoJsonGeometryCollection2D, bbox: geoJsonPoint2DWithBbox.bbox, @@ -40,8 +35,3 @@ export const multiGeoJsonGeometryCollection3DWithBbox: GeoJSONGeometryCollection ...multiGeoJsonGeometryCollection3D, bbox: [0.0, 0.0, 0.0, 20.0, 10.0, 10.0], }; - -export const nestedGeoJsonGeometryCollectionWithBbox: GeoJSONGeometryCollection = { - ...nestedGeoJsonGeometryCollection, - bbox: [-3.0, -2.0, 30.0, 30.0], -}; diff --git a/examples/geometry/multi_polygon.ts b/examples/geometry/multi_polygon.ts index fd46c2c..0ac4927 100644 --- a/examples/geometry/multi_polygon.ts +++ b/examples/geometry/multi_polygon.ts @@ -1,4 +1,4 @@ -import { GeoJSONMultiPolygon } from "../../src"; +import { GeoJSON2DMultiPolygon, GeoJSONMultiPolygon } from "../../src"; import { geoJsonPolygon2D, geoJsonPolygon2DWithBbox, @@ -8,7 +8,7 @@ import { geoJsonPolygon3DWithBbox, } from "./polygon"; -export const singleGeoJsonMultiPolygon2D: GeoJSONMultiPolygon = { +export const singleGeoJsonMultiPolygon2D: GeoJSON2DMultiPolygon = { type: "MultiPolygon", coordinates: [geoJsonPolygon2D.coordinates], }; diff --git a/examples/geometry/polygon.ts b/examples/geometry/polygon.ts index 1fcf867..8d2feaf 100644 --- a/examples/geometry/polygon.ts +++ b/examples/geometry/polygon.ts @@ -1,6 +1,6 @@ -import { GeoJSONPolygon } from "../../src"; +import { GeoJSON2DPolygon, GeoJSONPolygon } from "../../src"; -export const geoJsonPolygon2D: GeoJSONPolygon = { +export const geoJsonPolygon2D: GeoJSON2DPolygon = { type: "Polygon", coordinates: [ [ diff --git a/src/geometry/geometry_collection.ts b/src/geometry/geometry_collection.ts index 61a80ce..21de6bf 100644 --- a/src/geometry/geometry_collection.ts +++ b/src/geometry/geometry_collection.ts @@ -65,12 +65,14 @@ export const GeoJSONGeometryCollectionGenericSchema =

{ it("allows a geometry collection with multiple 3D geometries", () => { passGeoJSONGeometryCollectionTest(multiGeoJsonGeometryCollection3D); }); - it("allows a geometry collection with nested geometry collection", () => { - passGeoJSONGeometryCollectionTest(nestedGeoJsonGeometryCollection); - }); it("allows a geometry collection with one 2D geometry and valid bbox", () => { passGeoJSONGeometryCollectionTest(singleGeoJsonGeometryCollection2DWithBbox); }); @@ -45,9 +40,6 @@ describe("GeoJSONGeometryCollection", () => { it("allows a geometry collection with multiple 3D geometries and valid bbox", () => { passGeoJSONGeometryCollectionTest(multiGeoJsonGeometryCollection3DWithBbox); }); - it("allows a geometry collection with nested geometry collection and valid bbox", () => { - passGeoJSONGeometryCollectionTest(nestedGeoJsonGeometryCollectionWithBbox); - }); it("allows a geometry collection and preserves extra keys", () => { passGeoJSONGeometryCollectionTest({ ...singleGeoJsonGeometryCollection2D, From d1c659b37341bab60f876c8630a2c88ea0e5d5b7 Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Wed, 2 Oct 2024 22:18:10 +0200 Subject: [PATCH 5/8] Update README --- README.md | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++- src/index.ts | 3 +-- 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 88b0b78..7d84e25 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ const schema = GeoJSONSchema.parse({ }); ``` -This library also exposes schemas for the individual GeoJSON types: +This library exposes schemas for the individual GeoJSON types: ```typescript import { @@ -72,6 +72,33 @@ import type { } from "zod-geojson"; ``` +### Dimensionality + +This library exports specific schemas for 2D and 3D geometries, and their accompanying types: + +```typescript +import type { + GeoJSON2DFeatureSchema, + GeoJSON2DFeature, + // ... + GeoJSON2DPointSchema, + GeoJSON2DPoint, +} from "zod-geojson"; +``` + +If you wish the use a different dimension, the generic schemas are also exposed and you can +use them to create your own schemas and types: + +```typescript +import { GeoJSONGeometryGenericSchema } from "zod-geojson"; + +const GeoJSON4DPositionSchema = z.tuple([z.number(), z.number(), z.number(), z.number()]); +type GeoJSON4DPosition = z.infer; + +const GeoJSON4DGeometrySchema = GeoJSONGeometryGenericSchema(GeoJSON4DPositionSchema); +type GeoJSON4DGeometry = z.infer; +``` + ## Error Cases This library will throw an error if the GeoJSON object is not valid. For example, where the coordinates type does @@ -102,6 +129,41 @@ const schema = GeoJSONSchema.parse({ }); ``` +## Shortcomings + +This library does not support the validation of nested geometry collections. E.g. + +```typescript +// This will fail +const schema = GeoJSONSchema.parse({ + type: "GeometryCollection", + geometries: [ + { + type: "GeometryCollection", + geometries: [ + { + type: "Point", + coordinates: [0, 0], + }, + ], + }, + ], + bbox: [0, 0, 1, 1], +}); +``` + +This is per the GeoJSON RFC [recommendation](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.8): + +> To maximize interoperability, implementations SHOULD avoid nested GeometryCollections. + +and also because the implementation of recursive zod schemas together with generics is quite cumbersome and would +needlessy complicate both the implementation and usage of this library. If you need to validate nested geometry +collections feel free to open an issue and we can discuss possible solutions. + ## Contributing If you find any issues with the schemas or want to add new features, feel free to open an issue or a pull request. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/src/index.ts b/src/index.ts index e911416..49460da 100644 --- a/src/index.ts +++ b/src/index.ts @@ -15,5 +15,4 @@ export const GeoJSONSchema = GeoJSONGeometrySchema.or(GeoJSONFeatureSchema).or(G export type GeoJSON = z.infer; -// TODO: Performance testing & optimisation (replace callback iterators with for loops?) -// TODO: Compare performance with and without refinements +// TODO: Improve error messages From b3028449c621500129c8c45a6a3004808a5c911a Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Wed, 2 Oct 2024 22:33:33 +0200 Subject: [PATCH 6/8] Add dimensionality to feature and feature collection --- README.md | 7 ++++ src/feature.ts | 48 +++++++++++++-------- src/feature_collection.ts | 65 +++++++++++++++++------------ src/geometry/geometry_collection.ts | 20 ++++----- 4 files changed, 84 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 7d84e25..3cb504a 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,13 @@ const schema = GeoJSONSchema.parse({ ## Shortcomings +### Error messages + +The error messages are currently very big and not user-friendly due to the default handling of failures in +nested zod unions. This is something I hope to improve in the future. + +### Nested Geometry Collections + This library does not support the validation of nested geometry collections. E.g. ```typescript diff --git a/src/feature.ts b/src/feature.ts index 2a4647d..7406e96 100644 --- a/src/feature.ts +++ b/src/feature.ts @@ -1,7 +1,10 @@ import { z } from "zod"; import { GeoJSONBaseSchema } from "./base"; -import { GeoJSONGeometry, GeoJSONGeometrySchema } from "./geometry"; +import { GeoJSONGeometry, GeoJSONGeometryGenericSchema } from "./geometry"; import { bboxEquals, getBboxForGeometry, INVALID_BBOX_ISSUE } from "./geometry/validation/bbox"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; + +export type ValidatableGeoJSONFeature = { bbox?: number[]; geometry: GeoJSONGeometry | null }; const INVALID_FEATURE_KEYS_ISSUE = { code: "custom" as const, @@ -12,7 +15,7 @@ function validFeatureKeys(feature: Record): boolean { return !("coordinates" in feature) && !("features" in feature) && !("geometries" in feature); } -function validFeatureBbox({ bbox, geometry }: { bbox?: number[]; geometry: GeoJSONGeometry | null }): boolean { +function validFeatureBbox({ bbox, geometry }: ValidatableGeoJSONFeature): boolean { if (!bbox || !geometry) { return true; } @@ -20,21 +23,30 @@ function validFeatureBbox({ bbox, geometry }: { bbox?: number[]; geometry: GeoJS return bboxEquals(expectedBbox, bbox); } -export const GeoJSONFeatureSchema = GeoJSONBaseSchema.extend({ - id: z.string().or(z.number()).optional(), - type: z.literal("Feature"), - geometry: GeoJSONGeometrySchema.nullable(), - properties: z.object({}).passthrough().nullable(), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validFeatureKeys(val)) { - ctx.addIssue(INVALID_FEATURE_KEYS_ISSUE); - } - - if (!validFeatureBbox(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - } - }); +export const GeoJSONFeatureGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + id: z.string().or(z.number()).optional(), + type: z.literal("Feature"), + geometry: GeoJSONGeometryGenericSchema(positionSchema).nullable(), + properties: z.object({}).passthrough().nullable(), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validFeatureKeys(val)) { + ctx.addIssue(INVALID_FEATURE_KEYS_ISSUE); + } + + // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics + if (!validFeatureBbox(val as ValidatableGeoJSONFeature)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + } + }); +export const GeoJSONFeatureSchema = GeoJSONFeatureGenericSchema(GeoJSONPositionSchema); export type GeoJSONFeature = z.infer; + +export const GeoJSON2DFeatureSchema = GeoJSONFeatureGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DFeature = z.infer; + +export const GeoJSON3DFeatureSchema = GeoJSONFeatureGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DFeature = z.infer; diff --git a/src/feature_collection.ts b/src/feature_collection.ts index 95c71e5..9910040 100644 --- a/src/feature_collection.ts +++ b/src/feature_collection.ts @@ -1,10 +1,13 @@ import { z } from "zod"; import { GeoJSONBaseSchema } from "./base"; import { GeoJSONBbox } from "./bbox"; -import { GeoJSONFeature, GeoJSONFeatureSchema } from "./feature"; +import { GeoJSONFeature, GeoJSONFeatureGenericSchema } from "./feature"; import { GeoJSONGeometry } from "./geometry"; import { bboxEquals, getBboxForGeometries, INVALID_BBOX_ISSUE } from "./geometry/validation/bbox"; import { getDimensionForGeometry } from "./geometry/validation/dimension"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; + +type ValidatableGeoJSONFeatureCollection = { features: GeoJSONFeature[]; bbox?: GeoJSONBbox }; const INVALID_FEATURE_COLLECTION_KEYS_ISSUE = { code: "custom" as const, @@ -16,7 +19,7 @@ const INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE = { message: "Invalid dimensions. All features in feature collection must have the same dimension.", }; -function getGeometries({ features }: { features: GeoJSONFeature[] }): GeoJSONGeometry[] { +function getGeometries({ features }: ValidatableGeoJSONFeatureCollection): GeoJSONGeometry[] { return features.map((feature) => feature.geometry).filter((x): x is GeoJSONGeometry => x != null); } @@ -29,13 +32,13 @@ function validFeatureCollection(collection: Record): boolean { ); } -function validFeatureCollectionDimensions(collection: { features: GeoJSONFeature[] }): boolean { +function validFeatureCollectionDimensions(collection: ValidatableGeoJSONFeatureCollection): boolean { const geometries = getGeometries(collection); const dimension = getDimensionForGeometry(geometries[0]); return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); } -function validFeatureCollectionBbox({ features, bbox }: { features: GeoJSONFeature[]; bbox?: GeoJSONBbox }) { +function validFeatureCollectionBbox({ features, bbox }: ValidatableGeoJSONFeatureCollection) { if (!bbox) { return true; } @@ -43,30 +46,40 @@ function validFeatureCollectionBbox({ features, bbox }: { features: GeoJSONFeatu return bboxEquals(expectedBbox, bbox); } -export const GeoJSONFeatureCollectionSchema = GeoJSONBaseSchema.extend({ - type: z.literal("FeatureCollection"), - features: z.array(GeoJSONFeatureSchema), -}) - .passthrough() - .superRefine((val, ctx) => { - if (!validFeatureCollection(val)) { - ctx.addIssue(INVALID_FEATURE_COLLECTION_KEYS_ISSUE); - return; - } +export const GeoJSONFeatureCollectionGenericSchema =

(positionSchema: z.ZodSchema

) => + GeoJSONBaseSchema.extend({ + type: z.literal("FeatureCollection"), + features: z.array(GeoJSONFeatureGenericSchema(positionSchema)), + }) + .passthrough() + .superRefine((val, ctx) => { + if (!validFeatureCollection(val)) { + ctx.addIssue(INVALID_FEATURE_COLLECTION_KEYS_ISSUE); + return; + } - if (!val.features.length) { - return; - } + if (!val.features.length) { + return; + } - if (!validFeatureCollectionDimensions(val)) { - ctx.addIssue(INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE); - return; - } + // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics + if (!validFeatureCollectionDimensions(val as ValidatableGeoJSONFeatureCollection)) { + ctx.addIssue(INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE); + return; + } - if (!validFeatureCollectionBbox(val)) { - ctx.addIssue(INVALID_BBOX_ISSUE); - return; - } - }); + // Type-cast is safe, but necessary because the type of val is not inferred correctly due to the generics + if (!validFeatureCollectionBbox(val as ValidatableGeoJSONFeatureCollection)) { + ctx.addIssue(INVALID_BBOX_ISSUE); + return; + } + }); +export const GeoJSONFeatureCollectionSchema = GeoJSONFeatureCollectionGenericSchema(GeoJSONPositionSchema); export type GeoJSONFeatureCollection = z.infer; + +export const GeoJSON2DFeatureCollectionSchema = GeoJSONFeatureCollectionGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DFeatureCollection = z.infer; + +export const GeoJSON3DFeatureCollectionSchema = GeoJSONFeatureCollectionGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DFeatureCollection = z.infer; diff --git a/src/geometry/geometry_collection.ts b/src/geometry/geometry_collection.ts index 21de6bf..7954075 100644 --- a/src/geometry/geometry_collection.ts +++ b/src/geometry/geometry_collection.ts @@ -6,6 +6,8 @@ import { GeoJSONBaseSchema } from "../base"; import { GeoJSONGeometry } from "./index"; import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../position"; +type ValidatableGeometryCollection = { geometries: GeoJSONGeometry[]; bbox?: number[] }; + const INVALID_GEOMETRY_COLLECTION_KEYS_ISSUE = { code: "custom" as const, message: @@ -26,19 +28,13 @@ function validGeometryCollectionKeys(collection: Record): boole ); } -function validGeometryCollectionDimension({ geometries }: { geometries?: GeoJSONGeometry[] }): boolean { +function validGeometryCollectionDimension({ geometries }: ValidatableGeometryCollection): boolean { if (geometries == null) return false; let dimension = getDimensionForGeometry(geometries[0]); return geometries.slice(1).every((geometry) => getDimensionForGeometry(geometry) === dimension); } -function validGeometryCollectionBbox({ - bbox, - geometries, -}: { - bbox?: number[]; - geometries: GeoJSONGeometry[]; -}): boolean { +function validGeometryCollectionBbox({ bbox, geometries }: ValidatableGeometryCollection): boolean { if (!bbox) { return true; } @@ -65,14 +61,14 @@ export const GeoJSONGeometryCollectionGenericSchema =

Date: Wed, 2 Oct 2024 23:05:04 +0200 Subject: [PATCH 7/8] Add dimensionality to geojson + fix multi line string --- src/geometry/multi_line_string.ts | 8 ++++---- src/index.ts | 21 +++++++++++++++++---- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/geometry/multi_line_string.ts b/src/geometry/multi_line_string.ts index 6d7ce83..e7048cc 100644 --- a/src/geometry/multi_line_string.ts +++ b/src/geometry/multi_line_string.ts @@ -35,8 +35,8 @@ export const GeoJSONMultiLineStringGenericSchema =

(p export const GeoJSONMultiLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSONPositionSchema); export type GeoJSONMultiLineString = z.infer; -export const GeoJSONMulti2DLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSON2DPositionSchema); -export type GeoJSON2DMultiLineString = z.infer; +export const GeoJSON2DMultiLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2DMultiLineString = z.infer; -export const GeoJSONMulti3DLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSON3DPositionSchema); -export type GeoJSON3DMultiLineString = z.infer; +export const GeoJSON3DMultiLineStringSchema = GeoJSONMultiLineStringGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3DMultiLineString = z.infer; diff --git a/src/index.ts b/src/index.ts index 49460da..938b8ad 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,9 @@ // Derived from the GeoJSON spec: https://datatracker.ietf.org/doc/html/rfc7946 import { z } from "zod"; -import { GeoJSONFeatureSchema } from "./feature"; -import { GeoJSONFeatureCollectionSchema } from "./feature_collection"; -import { GeoJSONGeometrySchema } from "./geometry"; +import { GeoJSONFeatureGenericSchema } from "./feature"; +import { GeoJSONFeatureCollectionGenericSchema } from "./feature_collection"; +import { GeoJSONGeometryGenericSchema } from "./geometry"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "./position"; export * from "./feature"; export * from "./feature_collection"; @@ -11,8 +12,20 @@ export * from "./bbox"; export * from "./position"; export * from "./type"; -export const GeoJSONSchema = GeoJSONGeometrySchema.or(GeoJSONFeatureSchema).or(GeoJSONFeatureCollectionSchema); +export const GeoJSONGenericSchema =

(positionSchema: z.ZodSchema

) => + z.union([ + GeoJSONGeometryGenericSchema(positionSchema), + GeoJSONFeatureGenericSchema(positionSchema), + GeoJSONFeatureCollectionGenericSchema(positionSchema), + ]); +export const GeoJSONSchema = GeoJSONGenericSchema(GeoJSONPositionSchema); export type GeoJSON = z.infer; +export const GeoJSON2DSchema = GeoJSONGenericSchema(GeoJSON2DPositionSchema); +export type GeoJSON2D = z.infer; + +export const GeoJSON3DSchema = GeoJSONGenericSchema(GeoJSON3DPositionSchema); +export type GeoJSON3D = z.infer; + // TODO: Improve error messages From 4cac3c97e5c293e8991f02b7109ea89409fc470f Mon Sep 17 00:00:00 2001 From: Reinert Lemmens Date: Wed, 2 Oct 2024 23:05:18 +0200 Subject: [PATCH 8/8] Update all examples and tests with dimensionality --- examples/feature_collection.ts | 24 +++++++-- examples/geometry/geometry_collection.ts | 14 ++--- examples/geometry/line_string.ts | 10 ++-- examples/geometry/multi_line_string.ts | 14 ++--- examples/geometry/multi_point.ts | 10 ++-- examples/geometry/multi_polygon.ts | 12 ++--- examples/geometry/point.ts | 10 ++-- examples/geometry/polygon.ts | 12 ++--- tests/feature.test.ts | 20 ++++++- tests/feature_collection.test.ts | 61 ++++++++++++++++------ tests/geojson.test.ts | 24 +++++++-- tests/geometry/geometry_collection.test.ts | 31 ++++++++++- tests/geometry/line_string.test.ts | 23 +++++++- tests/geometry/multi_line_string.test.ts | 31 ++++++++++- tests/geometry/multi_point.test.ts | 23 +++++++- tests/geometry/multi_polygon.test.ts | 23 +++++++- tests/geometry/point.test.ts | 23 +++++++- tests/geometry/polygon.test.ts | 23 +++++++- tests/position.test.ts | 26 +++++++-- 19 files changed, 330 insertions(+), 84 deletions(-) diff --git a/examples/feature_collection.ts b/examples/feature_collection.ts index b6ac493..9f211df 100644 --- a/examples/feature_collection.ts +++ b/examples/feature_collection.ts @@ -1,6 +1,6 @@ -import { GeoJSONFeatureCollection } from "../src"; +import { GeoJSON2DFeatureCollection, GeoJSON3DFeatureCollection, GeoJSONFeatureCollection } from "../src"; -export const singleGeoJsonFeatureCollection: GeoJSONFeatureCollection = { +export const singleGeoJsonFeatureCollection2D: GeoJSON2DFeatureCollection = { type: "FeatureCollection", features: [ { @@ -14,7 +14,21 @@ export const singleGeoJsonFeatureCollection: GeoJSONFeatureCollection = { ], }; -export const multiGeoJsonFeatureCollection: GeoJSONFeatureCollection = { +export const singleGeoJsonFeatureCollection3D: GeoJSON3DFeatureCollection = { + type: "FeatureCollection", + features: [ + { + type: "Feature", + properties: {}, + geometry: { + type: "Point", + coordinates: [0.0, 0.0, 1.0], + }, + }, + ], +}; + +export const multiGeoJsonFeatureCollection2D: GeoJSON2DFeatureCollection = { type: "FeatureCollection", features: [ { @@ -39,7 +53,7 @@ export const multiGeoJsonFeatureCollection: GeoJSONFeatureCollection = { ], }; -export const multiGeoJsonFeatureCollectionWithBbox: GeoJSONFeatureCollection = { - ...multiGeoJsonFeatureCollection, +export const multiGeoJsonFeatureCollectionWithBbox2D: GeoJSONFeatureCollection = { + ...multiGeoJsonFeatureCollection2D, bbox: [0.0, 0.0, 10.0, 10.0], }; diff --git a/examples/geometry/geometry_collection.ts b/examples/geometry/geometry_collection.ts index 729a580..e0d57b5 100644 --- a/examples/geometry/geometry_collection.ts +++ b/examples/geometry/geometry_collection.ts @@ -1,4 +1,4 @@ -import { GeoJSONGeometryCollection } from "../../src"; +import { GeoJSON2DGeometryCollection, GeoJSON3DGeometryCollection } from "../../src"; import { geoJsonLineString3D } from "./line_string"; import { multiGeoJsonMultiLineString2D } from "./multi_line_string"; import { geoJsonMultiPoint2D } from "./multi_point"; @@ -6,32 +6,32 @@ import { singleGeoJsonMultiPolygon3D } from "./multi_polygon"; import { geoJsonPoint2D, geoJsonPoint2DWithBbox, geoJsonPoint3D } from "./point"; import { geoJsonPolygon2D } from "./polygon"; -export const singleGeoJsonGeometryCollection2D: GeoJSONGeometryCollection = { +export const singleGeoJsonGeometryCollection2D: GeoJSON2DGeometryCollection = { type: "GeometryCollection", geometries: [geoJsonPoint2D], }; -export const multiGeoJsonGeometryCollection2D: GeoJSONGeometryCollection = { +export const multiGeoJsonGeometryCollection2D: GeoJSON2DGeometryCollection = { type: "GeometryCollection", geometries: [geoJsonPoint2D, geoJsonMultiPoint2D, geoJsonPolygon2D, multiGeoJsonMultiLineString2D], }; -export const multiGeoJsonGeometryCollection3D: GeoJSONGeometryCollection = { +export const multiGeoJsonGeometryCollection3D: GeoJSON3DGeometryCollection = { type: "GeometryCollection", geometries: [geoJsonPoint3D, geoJsonLineString3D, singleGeoJsonMultiPolygon3D], }; -export const singleGeoJsonGeometryCollection2DWithBbox: GeoJSONGeometryCollection = { +export const singleGeoJsonGeometryCollection2DWithBbox: GeoJSON2DGeometryCollection = { ...singleGeoJsonGeometryCollection2D, bbox: geoJsonPoint2DWithBbox.bbox, }; -export const multiGeoJsonGeometryCollection2DWithBbox: GeoJSONGeometryCollection = { +export const multiGeoJsonGeometryCollection2DWithBbox: GeoJSON2DGeometryCollection = { ...multiGeoJsonGeometryCollection2D, bbox: [-3.0, -2.0, 30.0, 30.0], }; -export const multiGeoJsonGeometryCollection3DWithBbox: GeoJSONGeometryCollection = { +export const multiGeoJsonGeometryCollection3DWithBbox: GeoJSON3DGeometryCollection = { ...multiGeoJsonGeometryCollection3D, bbox: [0.0, 0.0, 0.0, 20.0, 10.0, 10.0], }; diff --git a/examples/geometry/line_string.ts b/examples/geometry/line_string.ts index 08746e3..c01a0b6 100644 --- a/examples/geometry/line_string.ts +++ b/examples/geometry/line_string.ts @@ -1,6 +1,6 @@ -import { GeoJSONLineString } from "../../src"; +import { GeoJSON2DLineString, GeoJSON3DLineString } from "../../src"; -export const geoJsonLineString2D: GeoJSONLineString = { +export const geoJsonLineString2D: GeoJSON2DLineString = { type: "LineString", coordinates: [ [1.0, 2.0], @@ -8,7 +8,7 @@ export const geoJsonLineString2D: GeoJSONLineString = { ], }; -export const geoJsonLineString3D: GeoJSONLineString = { +export const geoJsonLineString3D: GeoJSON3DLineString = { type: "LineString", coordinates: [ [0.0, 0.0, 0.0], @@ -17,12 +17,12 @@ export const geoJsonLineString3D: GeoJSONLineString = { ], }; -export const geoJsonLineString2DWithBbox: GeoJSONLineString = { +export const geoJsonLineString2DWithBbox: GeoJSON2DLineString = { ...geoJsonLineString2D, bbox: [1.0, 2.0, 3.0, 4.0], }; -export const geoJsonLineString3DWithBbox: GeoJSONLineString = { +export const geoJsonLineString3DWithBbox: GeoJSON3DLineString = { ...geoJsonLineString3D, bbox: [0.0, 0.0, 0.0, 20.0, 10.0, 2.0], }; diff --git a/examples/geometry/multi_line_string.ts b/examples/geometry/multi_line_string.ts index 5a4cbbe..f04dba7 100644 --- a/examples/geometry/multi_line_string.ts +++ b/examples/geometry/multi_line_string.ts @@ -1,4 +1,4 @@ -import { GeoJSONMultiLineString } from "../../src"; +import { GeoJSON2DMultiLineString, GeoJSON3DMultiLineString } from "../../src"; import { geoJsonLineString2D, geoJsonLineString2DWithBbox, @@ -6,11 +6,11 @@ import { geoJsonLineString3DWithBbox, } from "./line_string"; -export const singleGeoJsonMultiLineString2D: GeoJSONMultiLineString = { +export const singleGeoJsonMultiLineString2D: GeoJSON2DMultiLineString = { type: "MultiLineString", coordinates: [geoJsonLineString2D.coordinates], }; -export const multiGeoJsonMultiLineString2D: GeoJSONMultiLineString = { +export const multiGeoJsonMultiLineString2D: GeoJSON2DMultiLineString = { type: "MultiLineString", coordinates: [ geoJsonLineString2D.coordinates, @@ -20,22 +20,22 @@ export const multiGeoJsonMultiLineString2D: GeoJSONMultiLineString = { ], ], }; -export const singleGeoJsonMultiLineString3D: GeoJSONMultiLineString = { +export const singleGeoJsonMultiLineString3D: GeoJSON3DMultiLineString = { type: "MultiLineString", coordinates: [geoJsonLineString3D.coordinates], }; -export const singleGeoJsonMultiLineString2DWithBbox: GeoJSONMultiLineString = { +export const singleGeoJsonMultiLineString2DWithBbox: GeoJSON2DMultiLineString = { ...singleGeoJsonMultiLineString2D, bbox: geoJsonLineString2DWithBbox.bbox, }; -export const multiGeoJsonMultiLineString2DWithBbox: GeoJSONMultiLineString = { +export const multiGeoJsonMultiLineString2DWithBbox: GeoJSON2DMultiLineString = { ...multiGeoJsonMultiLineString2D, bbox: [1.0, 2.0, 30.0, 30.0], }; -export const singleGeoJsonMultiLineString3DWithBbox: GeoJSONMultiLineString = { +export const singleGeoJsonMultiLineString3DWithBbox: GeoJSON3DMultiLineString = { ...singleGeoJsonMultiLineString3D, bbox: geoJsonLineString3DWithBbox.bbox, }; diff --git a/examples/geometry/multi_point.ts b/examples/geometry/multi_point.ts index eda72a9..07a2a24 100644 --- a/examples/geometry/multi_point.ts +++ b/examples/geometry/multi_point.ts @@ -1,6 +1,6 @@ -import { GeoJSONMultiPoint } from "../../src"; +import { GeoJSON2DMultiPoint, GeoJSON3DMultiPoint } from "../../src"; -export const geoJsonMultiPoint2D: GeoJSONMultiPoint = { +export const geoJsonMultiPoint2D: GeoJSON2DMultiPoint = { type: "MultiPoint", coordinates: [ [0.0, 0.0], @@ -9,7 +9,7 @@ export const geoJsonMultiPoint2D: GeoJSONMultiPoint = { ], }; -export const geoJsonMultiPoint3D: GeoJSONMultiPoint = { +export const geoJsonMultiPoint3D: GeoJSON3DMultiPoint = { type: "MultiPoint", coordinates: [ [0.0, 0.0, 0.0], @@ -18,12 +18,12 @@ export const geoJsonMultiPoint3D: GeoJSONMultiPoint = { ], }; -export const geoJsonMultiPoint2DWithBbox: GeoJSONMultiPoint = { +export const geoJsonMultiPoint2DWithBbox: GeoJSON2DMultiPoint = { ...geoJsonMultiPoint2D, bbox: [-3.0, -2.0, 8.0, 4.0], }; -export const geoJsonMultiPoint3DWithBbox: GeoJSONMultiPoint = { +export const geoJsonMultiPoint3DWithBbox: GeoJSON3DMultiPoint = { ...geoJsonMultiPoint3D, bbox: [-3.0, -2.0, 0.0, 8.0, 4.0, 5.0], }; diff --git a/examples/geometry/multi_polygon.ts b/examples/geometry/multi_polygon.ts index 0ac4927..ea482a5 100644 --- a/examples/geometry/multi_polygon.ts +++ b/examples/geometry/multi_polygon.ts @@ -1,4 +1,4 @@ -import { GeoJSON2DMultiPolygon, GeoJSONMultiPolygon } from "../../src"; +import { GeoJSON2DMultiPolygon, GeoJSON3DMultiPolygon } from "../../src"; import { geoJsonPolygon2D, geoJsonPolygon2DWithBbox, @@ -13,27 +13,27 @@ export const singleGeoJsonMultiPolygon2D: GeoJSON2DMultiPolygon = { coordinates: [geoJsonPolygon2D.coordinates], }; -export const multiGeoJsonMultiPolygon2D: GeoJSONMultiPolygon = { +export const multiGeoJsonMultiPolygon2D: GeoJSON2DMultiPolygon = { type: "MultiPolygon", coordinates: [geoJsonPolygon2D.coordinates, geoJsonPolygon2DWithHole.coordinates], }; -export const singleGeoJsonMultiPolygon3D: GeoJSONMultiPolygon = { +export const singleGeoJsonMultiPolygon3D: GeoJSON3DMultiPolygon = { type: "MultiPolygon", coordinates: [geoJsonPolygon3D.coordinates], }; -export const singleGeoJsonMultiPolygon2DWithBbox: GeoJSONMultiPolygon = { +export const singleGeoJsonMultiPolygon2DWithBbox: GeoJSON2DMultiPolygon = { ...singleGeoJsonMultiPolygon2D, bbox: geoJsonPolygon2DWithBbox.bbox, }; -export const multiGeoJsonMultiPolygon2DWithBbox: GeoJSONMultiPolygon = { +export const multiGeoJsonMultiPolygon2DWithBbox: GeoJSON2DMultiPolygon = { ...multiGeoJsonMultiPolygon2D, bbox: geoJsonPolygon2DWithHoleAndBbox.bbox, }; -export const singleGeoJsonMultiPolygon3DWithBbox: GeoJSONMultiPolygon = { +export const singleGeoJsonMultiPolygon3DWithBbox: GeoJSON3DMultiPolygon = { ...singleGeoJsonMultiPolygon3D, bbox: geoJsonPolygon3DWithBbox.bbox, }; diff --git a/examples/geometry/point.ts b/examples/geometry/point.ts index 5a16dd5..ee0afc8 100644 --- a/examples/geometry/point.ts +++ b/examples/geometry/point.ts @@ -1,21 +1,21 @@ -import { GeoJSONPoint } from "../../src"; +import { GeoJSON2DPoint, GeoJSON3DPoint } from "../../src"; -export const geoJsonPoint2D: GeoJSONPoint = { +export const geoJsonPoint2D: GeoJSON2DPoint = { type: "Point", coordinates: [1.0, 2.0], }; -export const geoJsonPoint3D: GeoJSONPoint = { +export const geoJsonPoint3D: GeoJSON3DPoint = { type: "Point", coordinates: [1.0, 2.0, 10.0], }; -export const geoJsonPoint2DWithBbox: GeoJSONPoint = { +export const geoJsonPoint2DWithBbox: GeoJSON2DPoint = { ...geoJsonPoint2D, bbox: [1.0, 2.0, 1.0, 2.0], }; -export const geoJsonPoint3DWithBbox: GeoJSONPoint = { +export const geoJsonPoint3DWithBbox: GeoJSON3DPoint = { ...geoJsonPoint3D, bbox: [1.0, 2.0, 10.0, 1.0, 2.0, 10.0], }; diff --git a/examples/geometry/polygon.ts b/examples/geometry/polygon.ts index 8d2feaf..e35be6e 100644 --- a/examples/geometry/polygon.ts +++ b/examples/geometry/polygon.ts @@ -1,4 +1,4 @@ -import { GeoJSON2DPolygon, GeoJSONPolygon } from "../../src"; +import { GeoJSON2DPolygon, GeoJSON3DPolygon } from "../../src"; export const geoJsonPolygon2D: GeoJSON2DPolygon = { type: "Polygon", @@ -13,7 +13,7 @@ export const geoJsonPolygon2D: GeoJSON2DPolygon = { ], }; -export const geoJsonPolygon3D: GeoJSONPolygon = { +export const geoJsonPolygon3D: GeoJSON3DPolygon = { type: "Polygon", coordinates: [ [ @@ -26,7 +26,7 @@ export const geoJsonPolygon3D: GeoJSONPolygon = { ], }; -export const geoJsonPolygon2DWithHole: GeoJSONPolygon = { +export const geoJsonPolygon2DWithHole: GeoJSON2DPolygon = { ...geoJsonPolygon2D, coordinates: [ [ @@ -46,17 +46,17 @@ export const geoJsonPolygon2DWithHole: GeoJSONPolygon = { ], }; -export const geoJsonPolygon2DWithBbox: GeoJSONPolygon = { +export const geoJsonPolygon2DWithBbox: GeoJSON2DPolygon = { ...geoJsonPolygon2D, bbox: [0.0, 0.0, 1.0, 1.0], }; -export const geoJsonPolygon3DWithBbox: GeoJSONPolygon = { +export const geoJsonPolygon3DWithBbox: GeoJSON3DPolygon = { ...geoJsonPolygon3D, bbox: [0.0, 0.0, 0.0, 1.0, 2.0, 2.0], }; -export const geoJsonPolygon2DWithHoleAndBbox: GeoJSONPolygon = { +export const geoJsonPolygon2DWithHoleAndBbox: GeoJSON2DPolygon = { ...geoJsonPolygon2DWithHole, bbox: [0.0, 0.0, 10.0, 10.0], }; diff --git a/tests/feature.test.ts b/tests/feature.test.ts index 9642f7f..1b62252 100644 --- a/tests/feature.test.ts +++ b/tests/feature.test.ts @@ -6,7 +6,7 @@ import { geoJsonFeaturePolygon2D, geoJsonFeaturePolygon3DWithBbox, } from "../examples/feature"; -import { GeoJSONFeatureSchema } from "../src"; +import { GeoJSON2DFeatureSchema, GeoJSON3DFeatureSchema, GeoJSONFeatureSchema } from "../src"; function passGeoJSONFeatureSchemaTest(object: unknown) { expect(GeoJSONFeatureSchema.parse(object)).toEqual(object); @@ -146,4 +146,22 @@ describe("GeoJSONFeature", () => { bbox: ["bbox must not contain strings"], }); }); + + describe("2D", () => { + it("allows a 2D feature", () => { + expect(GeoJSON2DFeatureSchema.parse(geoJsonFeaturePolygon2D)).toEqual(geoJsonFeaturePolygon2D); + }); + it("does not allow a 3D feature", () => { + expect(() => GeoJSON2DFeatureSchema.parse(geoJsonFeaturePoint3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D feature", () => { + expect(GeoJSON3DFeatureSchema.parse(geoJsonFeaturePoint3D)).toEqual(geoJsonFeaturePoint3D); + }); + it("does not allow a 2D feature", () => { + expect(() => GeoJSON3DFeatureSchema.parse(geoJsonFeaturePolygon2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/feature_collection.test.ts b/tests/feature_collection.test.ts index b8095fc..d53c8bb 100644 --- a/tests/feature_collection.test.ts +++ b/tests/feature_collection.test.ts @@ -2,11 +2,16 @@ import { describe, expect, it } from "@jest/globals"; import { ZodError } from "zod"; import { geoJsonFeaturePoint2D, geoJsonFeaturePoint3D } from "../examples/feature"; import { - multiGeoJsonFeatureCollection, - multiGeoJsonFeatureCollectionWithBbox, - singleGeoJsonFeatureCollection, + multiGeoJsonFeatureCollection2D, + multiGeoJsonFeatureCollectionWithBbox2D, + singleGeoJsonFeatureCollection2D, + singleGeoJsonFeatureCollection3D, } from "../examples/feature_collection"; -import { GeoJSONFeatureCollectionSchema } from "../src"; +import { + GeoJSON2DFeatureCollectionSchema, + GeoJSON3DFeatureCollectionSchema, + GeoJSONFeatureCollectionSchema, +} from "../src"; function passGeoJSONFeatureCollectionSchemaTest(object: unknown) { expect(GeoJSONFeatureCollectionSchema.parse(object)).toEqual(object); @@ -17,61 +22,83 @@ function failGeoJSONFeatureCollectionSchemaTest(object: unknown) { describe("GeoJSONFeatureCollection", () => { it("allows a feature collection with one feature", () => { - passGeoJSONFeatureCollectionSchemaTest(singleGeoJsonFeatureCollection); + passGeoJSONFeatureCollectionSchemaTest(singleGeoJsonFeatureCollection2D); }); it("allows a feature collection with multiple features", () => { - passGeoJSONFeatureCollectionSchemaTest(multiGeoJsonFeatureCollection); + passGeoJSONFeatureCollectionSchemaTest(multiGeoJsonFeatureCollection2D); }); it("allows a feature collection and preserves extra keys", () => { passGeoJSONFeatureCollectionSchemaTest({ - ...singleGeoJsonFeatureCollection, + ...singleGeoJsonFeatureCollection2D, color: "#00FF00", }); }); it("allows a feature collection with multiple features and bbox", () => { - passGeoJSONFeatureCollectionSchemaTest(multiGeoJsonFeatureCollectionWithBbox); + passGeoJSONFeatureCollectionSchemaTest(multiGeoJsonFeatureCollectionWithBbox2D); }); it("allows a feature collection with empty features array", () => { - passGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection, features: [] }); + passGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection2D, features: [] }); }); it("does not allow a feature collection without features key", () => { failGeoJSONFeatureCollectionSchemaTest({ type: "FeatureCollection" }); }); it("does not allow a feature collection with the coordinates key", () => { - failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection, coordinates: [] }); + failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection2D, coordinates: [] }); }); it("does not allow a feature collection with the geometry key", () => { - failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection, geometry: {} }); + failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection2D, geometry: {} }); }); it("does not allow a feature collection with the properties key", () => { - failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection, properties: {} }); + failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection2D, properties: {} }); }); it("does not allow a feature collection with the geometries key", () => { - failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection, geometries: [] }); + failGeoJSONFeatureCollectionSchemaTest({ ...singleGeoJsonFeatureCollection2D, geometries: [] }); }); it("does not allow a feature collection with inconsistent position dimensions across features", () => { failGeoJSONFeatureCollectionSchemaTest({ - ...multiGeoJsonFeatureCollection, + ...multiGeoJsonFeatureCollection2D, features: [geoJsonFeaturePoint2D, geoJsonFeaturePoint3D], }); }); it("does not allow a feature with a geometry with incorrect bbox", () => { failGeoJSONFeatureCollectionSchemaTest({ - ...multiGeoJsonFeatureCollection, + ...multiGeoJsonFeatureCollection2D, bbox: [40, 40, 80, 80], }); }); it("does not allow a feature with a geometry with invalid bbox dimensions", () => { failGeoJSONFeatureCollectionSchemaTest({ - ...multiGeoJsonFeatureCollection, + ...multiGeoJsonFeatureCollection2D, bbox: [0.0, 0.0, 0.0, 10.0, 10.0, 0.0], }); }); it("does not allow a feature with a geometry with badly formatted bbox", () => { failGeoJSONFeatureCollectionSchemaTest({ - ...multiGeoJsonFeatureCollection, + ...multiGeoJsonFeatureCollection2D, bbox: ["bbox must not contain strings"], }); }); + + describe("2D", () => { + it("allows a 2D feature collection", () => { + expect(GeoJSON2DFeatureCollectionSchema.parse(singleGeoJsonFeatureCollection2D)).toEqual( + singleGeoJsonFeatureCollection2D, + ); + }); + it("does not allow a 3D feature collection", () => { + expect(() => GeoJSON2DFeatureCollectionSchema.parse(singleGeoJsonFeatureCollection3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D feature collection", () => { + expect(GeoJSON3DFeatureCollectionSchema.parse(singleGeoJsonFeatureCollection3D)).toEqual( + singleGeoJsonFeatureCollection3D, + ); + }); + it("does not allow a 2D feature collection", () => { + expect(() => GeoJSON3DFeatureCollectionSchema.parse(singleGeoJsonFeatureCollection2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geojson.test.ts b/tests/geojson.test.ts index c8a9b40..2bc0a1f 100644 --- a/tests/geojson.test.ts +++ b/tests/geojson.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from "@jest/globals"; import { ZodError } from "zod"; import { geoJsonFeaturePolygon2D } from "../examples/feature"; -import { multiGeoJsonFeatureCollection } from "../examples/feature_collection"; +import { multiGeoJsonFeatureCollection2D } from "../examples/feature_collection"; import { geoJsonPoint3D } from "../examples/geometry/point"; -import { GeoJSONSchema } from "../src"; +import { GeoJSON2DSchema, GeoJSON3DSchema, GeoJSONSchema } from "../src"; describe("GeoJSONSchema", () => { it("allows a basic geometry", () => { @@ -13,10 +13,28 @@ describe("GeoJSONSchema", () => { expect(GeoJSONSchema.parse(geoJsonFeaturePolygon2D)).toEqual(geoJsonFeaturePolygon2D); }); it("allows a basic feature collection", () => { - expect(GeoJSONSchema.parse(multiGeoJsonFeatureCollection)).toEqual(multiGeoJsonFeatureCollection); + expect(GeoJSONSchema.parse(multiGeoJsonFeatureCollection2D)).toEqual(multiGeoJsonFeatureCollection2D); }); it("does not allow a geojson with invalid type", () => { expect(() => GeoJSONSchema.parse({ type: "SkippityBoop" })).toThrow(ZodError); }); + + describe("2D", () => { + it("allows a 2D geojson", () => { + expect(GeoJSON2DSchema.parse(geoJsonFeaturePolygon2D)).toEqual(geoJsonFeaturePolygon2D); + }); + it("does not allow a 3D geojson", () => { + expect(() => GeoJSON2DSchema.parse(geoJsonPoint3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D geojson", () => { + expect(GeoJSON3DSchema.parse(geoJsonPoint3D)).toEqual(geoJsonPoint3D); + }); + it("does not allow a 2D geojson", () => { + expect(() => GeoJSON3DSchema.parse(geoJsonFeaturePolygon2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/geometry_collection.test.ts b/tests/geometry/geometry_collection.test.ts index 07ac704..0649bc0 100644 --- a/tests/geometry/geometry_collection.test.ts +++ b/tests/geometry/geometry_collection.test.ts @@ -1,4 +1,5 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { multiGeoJsonGeometryCollection2D, multiGeoJsonGeometryCollection2DWithBbox, @@ -10,7 +11,11 @@ import { import { geoJsonLineString3D } from "../../examples/geometry/line_string"; import { geoJsonMultiPoint2D } from "../../examples/geometry/multi_point"; import { geoJsonPoint2D } from "../../examples/geometry/point"; -import { GeoJSONGeometryCollectionSchema } from "../../src"; +import { + GeoJSON2DGeometryCollectionSchema, + GeoJSON3DGeometryCollectionSchema, + GeoJSONGeometryCollectionSchema, +} from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONGeometryCollectionTest(value: unknown): void { @@ -150,4 +155,26 @@ describe("GeoJSONGeometryCollection", () => { bbox: ["bbox cannot contain strings"], }); }); + + describe("2D", () => { + it("allows a 2D geometry collection", () => { + expect(GeoJSON2DGeometryCollectionSchema.parse(multiGeoJsonGeometryCollection2D)).toEqual( + multiGeoJsonGeometryCollection2D, + ); + }); + it("does not allow a 3D geometry collection", () => { + expect(() => GeoJSON2DGeometryCollectionSchema.parse(multiGeoJsonGeometryCollection3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D geometry collection", () => { + expect(GeoJSON3DGeometryCollectionSchema.parse(multiGeoJsonGeometryCollection3D)).toEqual( + multiGeoJsonGeometryCollection3D, + ); + }); + it("does not allow a 2D geometry collection", () => { + expect(() => GeoJSON3DGeometryCollectionSchema.parse(multiGeoJsonGeometryCollection2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/line_string.test.ts b/tests/geometry/line_string.test.ts index 81e563b..9ad81e9 100644 --- a/tests/geometry/line_string.test.ts +++ b/tests/geometry/line_string.test.ts @@ -1,11 +1,12 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { geoJsonLineString2D, geoJsonLineString2DWithBbox, geoJsonLineString3D, geoJsonLineString3DWithBbox, } from "../../examples/geometry/line_string"; -import { GeoJSONLineStringSchema } from "../../src"; +import { GeoJSON2DLineStringSchema, GeoJSON3DLineStringSchema, GeoJSONLineStringSchema } from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONLineStringTest(value: unknown): void { @@ -120,4 +121,22 @@ describe("GeoJSONLineString", () => { bbox: ["badformat"], }); }); + + describe("2D", () => { + it("allows a 2D line string", () => { + expect(GeoJSON2DLineStringSchema.parse(geoJsonLineString2D)).toEqual(geoJsonLineString2D); + }); + it("does not allow a 3D line string", () => { + expect(() => GeoJSON2DLineStringSchema.parse(geoJsonLineString3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D line string", () => { + expect(GeoJSON3DLineStringSchema.parse(geoJsonLineString3D)).toEqual(geoJsonLineString3D); + }); + it("does not allow a 2D line string", () => { + expect(() => GeoJSON3DLineStringSchema.parse(geoJsonLineString2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/multi_line_string.test.ts b/tests/geometry/multi_line_string.test.ts index 314b58c..fc3d837 100644 --- a/tests/geometry/multi_line_string.test.ts +++ b/tests/geometry/multi_line_string.test.ts @@ -1,4 +1,5 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { geoJsonLineString2D, geoJsonLineString3D } from "../../examples/geometry/line_string"; import { multiGeoJsonMultiLineString2D, @@ -8,7 +9,11 @@ import { singleGeoJsonMultiLineString3D, singleGeoJsonMultiLineString3DWithBbox, } from "../../examples/geometry/multi_line_string"; -import { GeoJSONMultiLineStringSchema } from "../../src"; +import { + GeoJSON2DMultiLineStringSchema, + GeoJSON3DMultiLineStringSchema, + GeoJSONMultiLineStringSchema, +} from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONMultiLineStringTest(value: unknown): void { @@ -133,4 +138,26 @@ describe("GeoJSONMultiLineString", () => { bbox: ["hello"], }); }); + + describe("2D", () => { + it("allows a 2D multi-line string", () => { + expect(GeoJSON2DMultiLineStringSchema.parse(singleGeoJsonMultiLineString2D)).toEqual( + singleGeoJsonMultiLineString2D, + ); + }); + it("does not allow a 3D multi-line string", () => { + expect(() => GeoJSON2DMultiLineStringSchema.parse(singleGeoJsonMultiLineString3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D multi-line string", () => { + expect(GeoJSON3DMultiLineStringSchema.parse(singleGeoJsonMultiLineString3D)).toEqual( + singleGeoJsonMultiLineString3D, + ); + }); + it("does not allow a 2D multi-line string", () => { + expect(() => GeoJSON3DMultiLineStringSchema.parse(singleGeoJsonMultiLineString2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/multi_point.test.ts b/tests/geometry/multi_point.test.ts index 1adbf9b..3a771ae 100644 --- a/tests/geometry/multi_point.test.ts +++ b/tests/geometry/multi_point.test.ts @@ -1,4 +1,5 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { geoJsonMultiPoint2D, geoJsonMultiPoint2DWithBbox, @@ -6,7 +7,7 @@ import { geoJsonMultiPoint3DWithBbox, } from "../../examples/geometry/multi_point"; import { geoJsonPoint2D, geoJsonPoint3D } from "../../examples/geometry/point"; -import { GeoJSONMultiPointSchema } from "../../src"; +import { GeoJSON2DMultiPointSchema, GeoJSON3DMultiPointSchema, GeoJSONMultiPointSchema } from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONMultiPointTest(value: unknown): void { @@ -117,4 +118,22 @@ describe("GeoJSONMultiPoint", () => { bbox: ["hello"], }); }); + + describe("2D", () => { + it("allows a 2D multi-point", () => { + expect(GeoJSON2DMultiPointSchema.parse(geoJsonMultiPoint2D)).toEqual(geoJsonMultiPoint2D); + }); + it("does not allow a 3D multi-point", () => { + expect(() => GeoJSON2DMultiPointSchema.parse(geoJsonMultiPoint3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D multi-point", () => { + expect(GeoJSON3DMultiPointSchema.parse(geoJsonMultiPoint3D)).toEqual(geoJsonMultiPoint3D); + }); + it("does not allow a 2D multi-point", () => { + expect(() => GeoJSON3DMultiPointSchema.parse(geoJsonMultiPoint2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/multi_polygon.test.ts b/tests/geometry/multi_polygon.test.ts index 1ab936c..146ce55 100644 --- a/tests/geometry/multi_polygon.test.ts +++ b/tests/geometry/multi_polygon.test.ts @@ -1,4 +1,5 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { multiGeoJsonMultiPolygon2D, multiGeoJsonMultiPolygon2DWithBbox, @@ -8,7 +9,7 @@ import { singleGeoJsonMultiPolygon3DWithBbox, } from "../../examples/geometry/multi_polygon"; import { geoJsonPolygon2D, geoJsonPolygon3D } from "../../examples/geometry/polygon"; -import { GeoJSONMultiPolygonSchema } from "../../src"; +import { GeoJSON2DMultiPolygonSchema, GeoJSON3DMultiPolygonSchema, GeoJSONMultiPolygonSchema } from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONMultiPolygonTest(value: unknown): void { @@ -170,4 +171,22 @@ describe("GeoJSONMultiPolygon", () => { bbox: ["hello"], }); }); + + describe("2D", () => { + it("allows a 2D multi-polygon", () => { + expect(GeoJSON2DMultiPolygonSchema.parse(singleGeoJsonMultiPolygon2D)).toEqual(singleGeoJsonMultiPolygon2D); + }); + it("does not allow a 3D multi-polygon", () => { + expect(() => GeoJSON2DMultiPolygonSchema.parse(singleGeoJsonMultiPolygon3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D multi-polygon", () => { + expect(GeoJSON3DMultiPolygonSchema.parse(singleGeoJsonMultiPolygon3D)).toEqual(singleGeoJsonMultiPolygon3D); + }); + it("does not allow a 2D multi-polygon", () => { + expect(() => GeoJSON3DMultiPolygonSchema.parse(singleGeoJsonMultiPolygon2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/point.test.ts b/tests/geometry/point.test.ts index 5ddab7e..d0a7a9e 100644 --- a/tests/geometry/point.test.ts +++ b/tests/geometry/point.test.ts @@ -1,11 +1,12 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { geoJsonPoint2D, geoJsonPoint2DWithBbox, geoJsonPoint3D, geoJsonPoint3DWithBbox, } from "../../examples/geometry/point"; -import { GeoJSONPointSchema } from "../../src"; +import { GeoJSON2DPointSchema, GeoJSON3DPointSchema, GeoJSONPointSchema } from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONPointTest(value: unknown): void { @@ -109,4 +110,22 @@ describe("GeoJSONPoint", () => { coordinates: [], }); }); + + describe("2D", () => { + it("allows a 2D point", () => { + expect(GeoJSON2DPointSchema.parse(geoJsonPoint2D)).toEqual(geoJsonPoint2D); + }); + it("does not allow a 3D point", () => { + expect(() => GeoJSON2DPointSchema.parse(geoJsonPoint3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D point", () => { + expect(GeoJSON3DPointSchema.parse(geoJsonPoint3D)).toEqual(geoJsonPoint3D); + }); + it("does not allow a 2D point", () => { + expect(() => GeoJSON3DPointSchema.parse(geoJsonPoint2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/geometry/polygon.test.ts b/tests/geometry/polygon.test.ts index bc667ef..d4f7464 100644 --- a/tests/geometry/polygon.test.ts +++ b/tests/geometry/polygon.test.ts @@ -1,11 +1,12 @@ -import { describe, it } from "@jest/globals"; +import { describe, expect, it } from "@jest/globals"; +import { ZodError } from "zod"; import { geoJsonPolygon2D, geoJsonPolygon2DWithHole, geoJsonPolygon2DWithHoleAndBbox, geoJsonPolygon3D, } from "../../examples/geometry/polygon"; -import { GeoJSONPolygonSchema } from "../../src"; +import { GeoJSON2DPolygonSchema, GeoJSON3DPolygonSchema, GeoJSONPolygonSchema } from "../../src"; import { failGeoJSONGeometrySchemaTest, passGeoJSONGeometrySchemaTest } from "./_helpers"; function passGeoJSONPolygonTest(value: unknown): void { @@ -166,4 +167,22 @@ describe("GeoJSONPolygon", () => { bbox: ["hello"], }); }); + + describe("2D", () => { + it("allows a 2D polygon", () => { + expect(GeoJSON2DPolygonSchema.parse(geoJsonPolygon2D)).toEqual(geoJsonPolygon2D); + }); + it("does not allow a 3D polygon", () => { + expect(() => GeoJSON2DPolygonSchema.parse(geoJsonPolygon3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D polygon", () => { + expect(GeoJSON3DPolygonSchema.parse(geoJsonPolygon3D)).toEqual(geoJsonPolygon3D); + }); + it("does not allow a 2D polygon", () => { + expect(() => GeoJSON3DPolygonSchema.parse(geoJsonPolygon2D)).toThrow(ZodError); + }); + }); }); diff --git a/tests/position.test.ts b/tests/position.test.ts index ebf2796..714e8a8 100644 --- a/tests/position.test.ts +++ b/tests/position.test.ts @@ -1,14 +1,16 @@ import { describe, expect, it } from "@jest/globals"; import { ZodError } from "zod"; -import { GeoJSONPosition, GeoJSONPositionSchema } from "../src"; +import { GeoJSON2DPositionSchema, GeoJSON3DPositionSchema, GeoJSONPosition, GeoJSONPositionSchema } from "../src"; + +const position2D: GeoJSONPosition = [0, 0]; + +const position3D: GeoJSONPosition = [1, 2, 3]; describe("GeoJSONPosition", () => { it("allows 2D positions", () => { - const position2D: GeoJSONPosition = [0, 0]; expect(GeoJSONPositionSchema.parse(position2D)).toEqual(position2D); }); it("allows 3D positions", () => { - const position3D: GeoJSONPosition = [1, 2, 3]; expect(GeoJSONPositionSchema.parse(position3D)).toEqual(position3D); }); it("allows unknown 4D positions", () => { @@ -23,4 +25,22 @@ describe("GeoJSONPosition", () => { it("does not allow empty positions", () => { expect(() => GeoJSONPositionSchema.parse([])).toThrow(ZodError); }); + + describe("2D", () => { + it("allows a 2D position", () => { + expect(GeoJSON2DPositionSchema.parse(position2D)).toEqual(position2D); + }); + it("does not allow a 3D position", () => { + expect(() => GeoJSON2DPositionSchema.parse(position3D)).toThrow(ZodError); + }); + }); + + describe("3D", () => { + it("allows a 3D position", () => { + expect(GeoJSON3DPositionSchema.parse(position3D)).toEqual(position3D); + }); + it("does not allow a 2D position", () => { + expect(() => GeoJSON3DPositionSchema.parse(position2D)).toThrow(ZodError); + }); + }); });