Improve MineGeo exploration and mapping features

This commit is contained in:
2026-10-05 13:41:36 +05:00
parent aec0641f87
commit c5368fa8cc
69 changed files with 3581 additions and 333 deletions

View File

@@ -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>;