Skip to content

Commit

Permalink
Merge pull request #6 from reilem/fix-position-and-bbox
Browse files Browse the repository at this point in the history
Fix position and bbox
  • Loading branch information
reilem authored Nov 1, 2024
2 parents 1dbd047 + 1252d6b commit 0d9e95b
Show file tree
Hide file tree
Showing 47 changed files with 719 additions and 392 deletions.
8 changes: 3 additions & 5 deletions examples/bbox.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { GeoJSONBbox } from "../src";
import { GeoJSON2DBbox, GeoJSON3DBbox } from "../src/bbox";

export const bbox2D: GeoJSONBbox = [0.0, 0.0, 1.0, 1.0];
export const bbox2D: GeoJSON2DBbox = [0.0, 0.0, 1.0, 1.0];

export const bbox3D: GeoJSONBbox = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];

export const bbox4D: GeoJSONBbox = [0.0, 0.0, 1.0, 3.0, 1.0, 2.0, 2.0, 4.0];
export const bbox3D: GeoJSON3DBbox = [0.0, 0.0, 1.0, 1.0, 2.0, 2.0];
13 changes: 13 additions & 0 deletions examples/feature.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { GeoJSONFeature } from "../src";
import { multiGeoJsonGeometryCollection2D, multiGeoJsonGeometryCollection3D } from "./geometry/geometry_collection";
import { geoJsonPoint2D, geoJsonPoint3D } from "./geometry/point";
import { geoJsonPolygon2D, geoJsonPolygon3D, geoJsonPolygon3DWithBbox } from "./geometry/polygon";

Expand Down Expand Up @@ -26,3 +27,15 @@ export const geoJsonFeaturePolygon3DWithBbox: GeoJSONFeature = {
geometry: geoJsonPolygon3D,
bbox: geoJsonPolygon3DWithBbox.bbox,
};

export const geoJsonFeatureGeometryCollection2D: GeoJSONFeature = {
type: "Feature",
properties: {},
geometry: multiGeoJsonGeometryCollection2D,
};

export const geoJsonFeatureGeometryCollection3D: GeoJSONFeature = {
type: "Feature",
properties: {},
geometry: multiGeoJsonGeometryCollection3D,
};
9 changes: 2 additions & 7 deletions examples/geometry/geometry_collection.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { GeoJSON2DGeometryCollection, GeoJSON3DGeometryCollection, GeoJSONGeometryCollection } from "../../src";
import { GeoJSON2DGeometryCollection, GeoJSON3DGeometryCollection } from "../../src";
import { geoJsonLineString3D } from "./line_string";
import { multiGeoJsonMultiLineString2D } from "./multi_line_string";
import { geoJsonMultiPoint2D } from "./multi_point";
import { singleGeoJsonMultiPolygon3D } from "./multi_polygon";
import { geoJsonPoint2D, geoJsonPoint2DWithBbox, geoJsonPoint3D, geoJsonPoint6D } from "./point";
import { geoJsonPoint2D, geoJsonPoint2DWithBbox, geoJsonPoint3D } from "./point";
import { geoJsonPolygon2D } from "./polygon";

export const singleGeoJsonGeometryCollection2D: GeoJSON2DGeometryCollection = {
Expand Down Expand Up @@ -35,8 +35,3 @@ export const multiGeoJsonGeometryCollection3DWithBbox: GeoJSON3DGeometryCollecti
...multiGeoJsonGeometryCollection3D,
bbox: [0.0, 0.0, 0.0, 20.0, 10.0, 10.0],
};

export const singleGeoJsonGeometryCollection6D: GeoJSONGeometryCollection = {
type: "GeometryCollection",
geometries: [geoJsonPoint6D],
};
10 changes: 1 addition & 9 deletions examples/geometry/line_string.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GeoJSON2DLineString, GeoJSON3DLineString, GeoJSONLineString } from "../../src";
import { GeoJSON2DLineString, GeoJSON3DLineString } from "../../src";

export const geoJsonLineString2D: GeoJSON2DLineString = {
type: "LineString",
Expand Down Expand Up @@ -26,11 +26,3 @@ export const geoJsonLineString3DWithBbox: GeoJSON3DLineString = {
...geoJsonLineString3D,
bbox: [0.0, 0.0, 0.0, 20.0, 10.0, 2.0],
};

export const geoJsonLineString5D: GeoJSONLineString = {
...geoJsonLineString2D,
coordinates: [
[0, 0, 0, 0, 0],
[1, 1, 1, 1, 1],
],
};
8 changes: 1 addition & 7 deletions examples/geometry/multi_line_string.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { GeoJSON2DMultiLineString, GeoJSON3DMultiLineString, GeoJSONMultiLineString } from "../../src";
import { GeoJSON2DMultiLineString, GeoJSON3DMultiLineString } from "../../src";
import {
geoJsonLineString2D,
geoJsonLineString2DWithBbox,
geoJsonLineString3D,
geoJsonLineString3DWithBbox,
geoJsonLineString5D,
} from "./line_string";

export const singleGeoJsonMultiLineString2D: GeoJSON2DMultiLineString = {
Expand Down Expand Up @@ -41,8 +40,3 @@ export const singleGeoJsonMultiLineString3DWithBbox: GeoJSON3DMultiLineString =
...singleGeoJsonMultiLineString3D,
bbox: geoJsonLineString3DWithBbox.bbox,
};

export const singleGeoJsonMultiLineString5D: GeoJSONMultiLineString = {
type: "MultiLineString",
coordinates: [geoJsonLineString5D.coordinates],
};
8 changes: 1 addition & 7 deletions examples/geometry/multi_point.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { GeoJSON2DMultiPoint, GeoJSON3DMultiPoint, GeoJSONMultiPoint } from "../../src";
import { geoJsonPoint6D } from "./point";
import { GeoJSON2DMultiPoint, GeoJSON3DMultiPoint } from "../../src";

export const geoJsonMultiPoint2D: GeoJSON2DMultiPoint = {
type: "MultiPoint",
Expand Down Expand Up @@ -28,8 +27,3 @@ export const geoJsonMultiPoint3DWithBbox: GeoJSON3DMultiPoint = {
...geoJsonMultiPoint3D,
bbox: [-3.0, -2.0, 0.0, 8.0, 4.0, 5.0],
};

export const geoJsonMultiPoint6D: GeoJSONMultiPoint = {
type: "MultiPoint",
coordinates: [geoJsonPoint6D.coordinates],
};
8 changes: 1 addition & 7 deletions examples/geometry/multi_polygon.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { GeoJSON2DMultiPolygon, GeoJSON3DMultiPolygon, GeoJSONMultiPolygon } from "../../src";
import { GeoJSON2DMultiPolygon, GeoJSON3DMultiPolygon } from "../../src";
import {
geoJsonPolygon2D,
geoJsonPolygon2DWithBbox,
geoJsonPolygon2DWithHole,
geoJsonPolygon2DWithHoleAndBbox,
geoJsonPolygon3D,
geoJsonPolygon3DWithBbox,
geoJsonPolygon4D,
} from "./polygon";

export const singleGeoJsonMultiPolygon2D: GeoJSON2DMultiPolygon = {
Expand Down Expand Up @@ -38,8 +37,3 @@ export const singleGeoJsonMultiPolygon3DWithBbox: GeoJSON3DMultiPolygon = {
...singleGeoJsonMultiPolygon3D,
bbox: geoJsonPolygon3DWithBbox.bbox,
};

export const singleGeoJsonMultiPolygon4D: GeoJSONMultiPolygon = {
type: "MultiPolygon",
coordinates: [geoJsonPolygon4D.coordinates],
};
7 changes: 1 addition & 6 deletions examples/geometry/point.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GeoJSON2DPoint, GeoJSON3DPoint, GeoJSONPoint } from "../../src";
import { GeoJSON2DPoint, GeoJSON3DPoint } from "../../src";

export const geoJsonPoint2D: GeoJSON2DPoint = {
type: "Point",
Expand All @@ -19,8 +19,3 @@ export const geoJsonPoint3DWithBbox: GeoJSON3DPoint = {
...geoJsonPoint3D,
bbox: [1.0, 2.0, 10.0, 1.0, 2.0, 10.0],
};

export const geoJsonPoint6D: GeoJSONPoint = {
...geoJsonPoint2D,
coordinates: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
};
15 changes: 1 addition & 14 deletions examples/geometry/polygon.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GeoJSON2DPolygon, GeoJSON3DPolygon, GeoJSONPolygon } from "../../src";
import { GeoJSON2DPolygon, GeoJSON3DPolygon } from "../../src";

export const geoJsonPolygon2D: GeoJSON2DPolygon = {
type: "Polygon",
Expand Down Expand Up @@ -60,16 +60,3 @@ export const geoJsonPolygon2DWithHoleAndBbox: GeoJSON2DPolygon = {
...geoJsonPolygon2DWithHole,
bbox: [0.0, 0.0, 10.0, 10.0],
};

export const geoJsonPolygon4D: GeoJSONPolygon = {
type: "Polygon",
coordinates: [
[
[0.0, 0.0, 0.0, 0.0],
[1.0, 0.0, 0.0, 0.0],
[1.0, 1.0, 2.0, 0.0],
[0.0, 2.0, 2.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
],
],
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/geojson": "^7946.0.14",
"jest": "^29.7.0",
"madge": "^8.0.0",
"prettier": "^3.2.5",
Expand Down
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 16 additions & 12 deletions src/base.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
import { z } from "zod";
import { GeoJSONBbox, GeoJSONBboxSchema, GeoJSONBboxSchemaType } from "./bbox";
import { GeoJSONBboxGenericSchema, GeoJSONBboxSchemaType, GeoJSONBboxGeneric } from "./bbox";
import { GeoJSONPosition } from "./geometry/position";

export type GeoJSONBase = {
bbox?: GeoJSONBbox;
export type GeoJSONBase<P extends GeoJSONPosition> = {
bbox?: GeoJSONBboxGeneric<P>;
};

export type GeoJSONBaseSchemaInnerType = {
bbox: z.ZodOptional<GeoJSONBboxSchemaType>;
export type GeoJSONBaseSchemaInnerType<P extends GeoJSONPosition> = {
bbox: z.ZodOptional<GeoJSONBboxSchemaType<P>>;
};

export type GeoJSONBaseSchemaType = z.ZodObject<
GeoJSONBaseSchemaInnerType,
export type GeoJSONBaseSchemaType<P extends GeoJSONPosition> = z.ZodObject<
GeoJSONBaseSchemaInnerType<P>,
"strip",
z.ZodTypeAny,
GeoJSONBase,
GeoJSONBase
GeoJSONBase<P>,
GeoJSONBase<P>
>;

export const GeoJSONBaseSchema: GeoJSONBaseSchemaType = z.object({
bbox: GeoJSONBboxSchema.optional(),
});
export const GeoJSONBaseSchema = <P extends GeoJSONPosition>(
positionSchema: z.ZodSchema<P>,
): GeoJSONBaseSchemaType<P> =>
z.object({
bbox: GeoJSONBboxGenericSchema(positionSchema).optional(),
});
49 changes: 41 additions & 8 deletions src/bbox.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,45 @@
import { z } from "zod";
import { z, ZodSchema } from "zod";
import {
GeoJSON2DPosition,
GeoJSON2DPositionSchema,
GeoJSON3DPosition,
GeoJSON3DPositionSchema,
GeoJSONPosition,
GeoJSONPositionSchema,
} from "./geometry/position";

export type GeoJSONBbox = [number, number, number, number, ...number[]];
export type GeoJSONBboxGeneric<P extends GeoJSONPosition> = P extends GeoJSON3DPosition
? [number, number, number, number, number, number]
: P extends GeoJSON2DPosition
? [number, number, number, number]
: [number, number, number, number] | [number, number, number, number, number, number];

export type GeoJSONBboxSchemaInnerType = z.ZodTuple<[z.ZodNumber, z.ZodNumber, z.ZodNumber, z.ZodNumber], z.ZodNumber>;
export type GeoJSONBboxSchemaType<P extends GeoJSONPosition> = ZodSchema<GeoJSONBboxGeneric<P>>;

export type GeoJSONBboxSchemaType = z.ZodEffects<GeoJSONBboxSchemaInnerType, GeoJSONBbox, GeoJSONBbox>;
const _2DBboxSchema = z.tuple([z.number(), z.number(), z.number(), z.number()]);
const _3DBboxSchema = z.tuple([z.number(), z.number(), z.number(), z.number(), z.number(), z.number()]);

export const GeoJSONBboxSchema: GeoJSONBboxSchemaType = z
.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");
/**
* Because zod cannot do conditional typing we need to do some hacky type casts to make this work
*/
export const GeoJSONBboxGenericSchema = <P extends GeoJSONPosition>(
positionSchema: z.ZodSchema<P>,
): GeoJSONBboxSchemaType<P> => {
// If the position is not a tuple, we can't infer the dimension, and we return a union of 2D and 3D bbox
if (!(positionSchema instanceof z.ZodTuple)) {
return z.union([_2DBboxSchema, _3DBboxSchema]) as unknown as ZodSchema<GeoJSONBboxGeneric<P>>;
}
if (positionSchema.items.length === 2) {
return _2DBboxSchema as unknown as ZodSchema<GeoJSONBboxGeneric<P>>;
}
return _3DBboxSchema as unknown as ZodSchema<GeoJSONBboxGeneric<P>>;
};

export const GeoJSON2DBboxSchema = GeoJSONBboxGenericSchema(GeoJSON2DPositionSchema);
export type GeoJSON2DBbox = z.infer<typeof GeoJSON2DBboxSchema>;

export const GeoJSON3DBboxSchema = GeoJSONBboxGenericSchema(GeoJSON3DPositionSchema);
export type GeoJSON3DBbox = z.infer<typeof GeoJSON3DBboxSchema>;

export const GeoJSONBboxSchema = GeoJSONBboxGenericSchema(GeoJSONPositionSchema);
export type GeoJSONBbox = z.infer<typeof GeoJSONBboxSchema>;
22 changes: 12 additions & 10 deletions src/feature.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,22 @@ import {
} from "./geometry/position";
import { GeoJSONTypeSchema } from "./type";
import { validBboxForFeature } from "./validation/bbox";
import { ValidatableFeature } from "./validation/types";

export const GeoJSONFeatureGenericSchema = <P extends GeoJSONPosition>(positionSchema: z.ZodSchema<P>) =>
GeoJSONBaseSchema.extend({
id: z.string().or(z.number()).optional(),
type: z.literal(GeoJSONTypeSchema.enum.Feature),
geometry: GeoJSONGeometryGenericSchema(positionSchema).nullable(),
properties: z.object({}).passthrough().nullable(),
coordinates: z.never({ message: "GeoJSON Feature cannot have a 'coordinates' key" }).optional(),
features: z.never({ message: "GeoJSON Feature cannot have a 'features' key" }).optional(),
geometries: z.never({ message: "GeoJSON Feature cannot have a 'geometries' key" }).optional(),
})
GeoJSONBaseSchema(positionSchema)
.extend({
id: z.string().or(z.number()).optional(),
type: z.literal(GeoJSONTypeSchema.enum.Feature),
geometry: GeoJSONGeometryGenericSchema(positionSchema).nullable(),
properties: z.object({}).passthrough().nullable(),
coordinates: z.never({ message: "GeoJSON Feature cannot have a 'coordinates' key" }).optional(),
features: z.never({ message: "GeoJSON Feature cannot have a 'features' key" }).optional(),
geometries: z.never({ message: "GeoJSON Feature cannot have a 'geometries' key" }).optional(),
})
.passthrough()
.superRefine((val, ctx) => {
if (!validBboxForFeature(val)) {
if (!validBboxForFeature(val as ValidatableFeature)) {
ctx.addIssue(INVALID_BBOX_ISSUE);
}
});
Expand Down
24 changes: 13 additions & 11 deletions src/feature_collection.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
import { z } from "zod";
import { GeoJSONBaseSchema } from "./base";
import { GeoJSONFeatureGenericSchema } from "./feature";
import { INVALID_BBOX_ISSUE } from "./geometry/validation/bbox";
import {
GeoJSON2DPositionSchema,
GeoJSON3DPositionSchema,
GeoJSONPosition,
GeoJSONPositionSchema,
} from "./geometry/position";
import { INVALID_BBOX_ISSUE } from "./geometry/validation/bbox";
import { GeoJSONTypeSchema } from "./type";
import { validBboxForFeatureCollection } from "./validation/bbox";
import {
INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE,
validDimensionsForFeatureCollection,
} from "./validation/dimension";
import { ValidatableFeatureCollection } from "./validation/types";

export const GeoJSONFeatureCollectionGenericSchema = <P extends GeoJSONPosition>(positionSchema: z.ZodSchema<P>) =>
GeoJSONBaseSchema.extend({
type: z.literal(GeoJSONTypeSchema.enum.FeatureCollection),
features: z.array(GeoJSONFeatureGenericSchema(positionSchema)),
coordinates: z.never({ message: "GeoJSON feature collection cannot have a 'coordinates' key" }).optional(),
geometry: z.never({ message: "GeoJSON feature collection cannot have a 'geometry' key" }).optional(),
properties: z.never({ message: "GeoJSON feature collection cannot have a 'properties' key" }).optional(),
geometries: z.never({ message: "GeoJSON feature collection cannot have a 'geometries' key" }).optional(),
})
GeoJSONBaseSchema(positionSchema)
.extend({
type: z.literal(GeoJSONTypeSchema.enum.FeatureCollection),
features: z.array(GeoJSONFeatureGenericSchema(positionSchema)),
coordinates: z.never({ message: "GeoJSON feature collection cannot have a 'coordinates' key" }).optional(),
geometry: z.never({ message: "GeoJSON feature collection cannot have a 'geometry' key" }).optional(),
properties: z.never({ message: "GeoJSON feature collection cannot have a 'properties' key" }).optional(),
geometries: z.never({ message: "GeoJSON feature collection cannot have a 'geometries' key" }).optional(),
})
.passthrough()
.superRefine((val, ctx) => {
if (!val.features.length) {
return;
}
if (!validDimensionsForFeatureCollection(val)) {
if (!validDimensionsForFeatureCollection(val as ValidatableFeatureCollection)) {
ctx.addIssue(INVALID_FEATURE_COLLECTION_DIMENSIONS_ISSUE);
return;
}
if (!validBboxForFeatureCollection(val)) {
if (!validBboxForFeatureCollection(val as ValidatableFeatureCollection)) {
ctx.addIssue(INVALID_BBOX_ISSUE);
return;
}
Expand Down
3 changes: 0 additions & 3 deletions src/geometry/geometry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ export const GeoJSONGeometryGenericSchema = <P extends GeoJSONPosition>(
GeoJSONSimpleGeometryGenericSchema(positionSchema),
GeoJSONGeometryCollectionGenericSchema(positionSchema),
]);
export type GeoJSONGenericGeometry<P extends GeoJSONPosition> = z.infer<
ReturnType<typeof GeoJSONGeometryGenericSchema<P>>
>;

export const GeoJSONGeometrySchema = GeoJSONGeometryGenericSchema(GeoJSONPositionSchema);
export type GeoJSONGeometry = z.infer<typeof GeoJSONGeometrySchema>;
Expand Down
Loading

0 comments on commit 0d9e95b

Please sign in to comment.