Improve MineGeo exploration and mapping features
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { normalizeServerAddress } from "./server-address";
|
||||
export const boundsSchema = z
|
||||
.object({
|
||||
minX: z.number().int(),
|
||||
@@ -20,6 +21,23 @@ export const pointSchema = z.object({
|
||||
z: z.number().finite(),
|
||||
});
|
||||
export const faces = ["px", "nx", "py", "ny", "pz", "nz"] as const;
|
||||
export const viewpointSchema = pointSchema.extend({
|
||||
id: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/),
|
||||
assetRoot: z.string().uuid().optional(),
|
||||
assetId: z
|
||||
.string()
|
||||
.regex(/^[a-zA-Z0-9_-]{1,64}$/)
|
||||
.optional(),
|
||||
contributorId: z.string().uuid().optional(),
|
||||
source: z.enum(["plugin", "fabric"]).optional(),
|
||||
});
|
||||
export function panoramaPath(
|
||||
root: string,
|
||||
point: z.infer<typeof viewpointSchema>,
|
||||
file: string,
|
||||
) {
|
||||
return `${point.assetRoot ?? root}/${point.assetId ?? point.id}/${file}`;
|
||||
}
|
||||
export const datasetSchema = z
|
||||
.object({
|
||||
format: z.literal("minegeo-dataset/v1"),
|
||||
@@ -32,20 +50,10 @@ export const datasetSchema = z
|
||||
serverAddress: z.string().max(255).optional(),
|
||||
bounds: boundsSchema,
|
||||
north: z.literal("-z"),
|
||||
viewpoints: z
|
||||
.array(
|
||||
pointSchema.extend({ id: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/) }),
|
||||
)
|
||||
.min(5)
|
||||
.max(100),
|
||||
contributionTo: z.string().uuid().optional(),
|
||||
viewpoints: z.array(viewpointSchema).min(1).max(24),
|
||||
// Retain the capture's locations while viewpoints holds the saved selection.
|
||||
availableViewpoints: z
|
||||
.array(
|
||||
pointSchema.extend({ id: z.string().regex(/^[a-zA-Z0-9_-]{1,64}$/) }),
|
||||
)
|
||||
.min(5)
|
||||
.max(100)
|
||||
.optional(),
|
||||
availableViewpoints: z.array(viewpointSchema).min(1).max(24).optional(),
|
||||
assets: z
|
||||
.array(
|
||||
z.object({
|
||||
@@ -64,15 +72,22 @@ export const datasetSchema = z
|
||||
.max(4096),
|
||||
})
|
||||
.superRefine((d, ctx) => {
|
||||
if (!d.contributionTo && d.viewpoints.length < 5)
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "Maps need at least five panoramas",
|
||||
});
|
||||
const paths = new Set(d.assets.map((asset) => asset.path));
|
||||
const requiredPaths =
|
||||
d.source === "plugin"
|
||||
? ["terrain.json.gz"]
|
||||
: [
|
||||
"map.png",
|
||||
...(d.availableViewpoints ?? d.viewpoints).flatMap((point) =>
|
||||
faces.map((face) => `${point.id}/${face}.png`),
|
||||
),
|
||||
...(d.availableViewpoints ?? d.viewpoints)
|
||||
.filter((point) => !point.assetRoot)
|
||||
.flatMap((point) =>
|
||||
faces.map((face) => `${point.id}/${face}.png`),
|
||||
),
|
||||
];
|
||||
for (const path of requiredPaths) {
|
||||
if (!paths.has(path))
|
||||
@@ -108,6 +123,23 @@ export const datasetSchema = z
|
||||
message: "Viewpoint outside map bounds",
|
||||
});
|
||||
});
|
||||
// Media provenance is assigned by the server after the owner accepts a contribution.
|
||||
export function parseUploadManifest(value: unknown) {
|
||||
const manifest = datasetSchema.parse(value);
|
||||
manifest.serverAddress =
|
||||
normalizeServerAddress(manifest.serverAddress) ?? undefined;
|
||||
for (const p of [
|
||||
...manifest.viewpoints,
|
||||
...(manifest.availableViewpoints ?? []),
|
||||
])
|
||||
if (p.assetRoot || p.assetId || p.contributorId || p.source)
|
||||
throw new Error("Uploads cannot specify server media provenance");
|
||||
if (manifest.availableViewpoints)
|
||||
throw new Error(
|
||||
"Upload all captured viewpoints; selection happens in the dashboard",
|
||||
);
|
||||
return manifest;
|
||||
}
|
||||
export type DatasetManifest = z.infer<typeof datasetSchema>;
|
||||
export type Bounds = z.infer<typeof boundsSchema>;
|
||||
export type Point = z.infer<typeof pointSchema>;
|
||||
|
||||
Reference in New Issue
Block a user