@seasketch/geoprocessing
Version:
Geoprocessing and reporting framework for SeaSketch 2.0
231 lines • 8.38 kB
JavaScript
import inquirer from "inquirer";
import { $ } from "zx";
import { importDatasource, readDatasources, } from "../base/datasources/index.js";
import { importVectorDatasourceOptionsSchema, importRasterDatasourceOptionsSchema, } from "../../src/index.js";
import path from "node:path";
import fs from "node:fs";
import { getProjectClient } from "../base/project/projectClient.js";
import { explodeQuestion } from "./explodeQuestion.js";
$.verbose = false;
// This is a standalone script used as a CLI command with a top-level function
const projectPath = process.argv[2];
const projectClient = getProjectClient(projectPath);
const datasources = readDatasources();
const geoTypeAnswer = await geoTypeQuestion();
const srcAnswer = await srcQuestion();
const options = await (async () => {
if (geoTypeAnswer.geo_type === "vector") {
// Vector datasource
const layerNameAnswer = await layerNameQuestion(srcAnswer.src);
const datasourceIdAnswer = await datasourceIdQuestion(datasources, srcAnswer.src);
const explodeAnswers = await explodeQuestion();
const { stdout } = await $ `ogrinfo -json -so -nocount -noextent -nogeomtype ${srcAnswer.src} ${layerNameAnswer.layerName}`;
const fields = JSON.parse(stdout)
.layers.find((layer) => layer.name === layerNameAnswer.layerName)
.fields.map((field) => field.name);
const detailedVectorAnswers = await detailedVectorQuestions(fields);
const remFields = fields.filter((f) => !detailedVectorAnswers.classKeys.includes(f));
// If there are fields left, ask user if they want to keep any of them
const vectorAnswerProperties = await (async () => {
if (remFields.length > 0) {
return vectorQuestionProperties(remFields);
}
else {
return { propertiesToKeep: [] };
}
})();
const options = vectorMapper({
...geoTypeAnswer,
...srcAnswer,
...datasourceIdAnswer,
...layerNameAnswer,
...detailedVectorAnswers,
...vectorAnswerProperties,
...explodeAnswers,
formats: ["fgb"],
precalc: true,
});
return options;
}
else {
// Raster datasource
const datasourceIdAnswer = await datasourceIdQuestion(datasources, srcAnswer.src);
const detailedRasterAnswers = await detailedRasterQuestions(srcAnswer.src);
const options = rasterMapper({
...geoTypeAnswer,
...srcAnswer,
...datasourceIdAnswer,
...detailedRasterAnswers,
precalc: true,
});
return options;
}
})();
await importDatasource(projectClient, options, {});
/** Maps answers object to options */
function vectorMapper(answers) {
const validOptions = importVectorDatasourceOptionsSchema.parse(answers);
return validOptions;
}
/** Maps answers object to options */
function rasterMapper(answers) {
const options = {
...answers,
};
// a blank noDataValue will end up as nan, so just remove it as its optional
if (Number.isNaN(Number.parseFloat(`${answers.noDataValue}`))) {
delete options.noDataValue;
}
const validOptions = importRasterDatasourceOptionsSchema.parse(options);
return validOptions;
}
/** Get Vector/Raster type */
async function geoTypeQuestion() {
return inquirer.prompt([
{
type: "list",
name: "geo_type",
message: "Type of data?",
choices: [
{
value: "vector",
name: "Vector",
},
{
value: "raster",
name: "Raster",
},
],
},
]);
}
/** Get src path */
async function srcQuestion() {
return inquirer.prompt([
{
type: "input",
name: "src",
message: "Enter path to src file (with filename)",
validate: (value) => {
const fullPath = path.resolve(projectPath, value);
if (!fs.existsSync(fullPath))
return "File does not exist";
else if (fs.statSync(fullPath).isFile()) {
return true;
}
else {
return "Path does not point to a file";
}
},
},
]);
}
/** Get datasourceId */
async function datasourceIdQuestion(datasources, srcPath) {
const datasourceIds = new Set(datasources.map((ds) => ds.datasourceId));
return inquirer.prompt([
{
type: "input",
name: "datasourceId",
message: "Choose unique datasource name (a-z, A-Z, 0-9, -, _), defaults to filename",
default: path.basename(srcPath, path.extname(srcPath)),
validate: (value) => value === "" ||
(!datasourceIds.has(value) &&
(/^[\w-]+$/.test(value)
? true
: "Invalid or duplicate datasource name")),
},
]);
}
/** Get layer name of vector file */
async function layerNameQuestion(srcPath) {
const { stdout } = await $ `ogrinfo -json ${srcPath}`;
const layers = JSON.parse(stdout).layers.map((layer) => layer.name);
if (!layers.length)
throw new Error(`No layers in vector, check validity of file.`);
return inquirer.prompt([
{
type: "list",
name: "layerName",
message: "Select layer to import",
choices: layers.map((layer) => ({
value: layer,
name: layer,
})),
},
]);
}
/** Get classKeys, propertiesToKeep, and formats */
async function detailedVectorQuestions(fields) {
const answers = await inquirer.prompt([
{
type: "checkbox",
name: "classKeys",
message: fields.length > 0
? "Select feature properties that you want to group metrics by"
: "No feature properties found in the vector layer. Press enter to continue.",
choices: fields.map((field) => ({
value: field,
name: field,
})),
},
]);
return {
...answers,
formats: [],
};
}
/** Get classKeys, propertiesToKeep, and formats */
async function vectorQuestionProperties(fields) {
return inquirer.prompt([
{
type: "checkbox",
name: "propertiesToKeep",
message: fields.length > 0
? "Select additional feature properties to keep in final datasource"
: "No feature properties found in the vector layer. Press enter to continue.",
choices: fields.map((field) => ({
value: field,
name: field,
})),
},
]);
}
/** Get measurementType, export formats, band, and noDataValue */
async function detailedRasterQuestions(srcPath) {
const { stdout } = await $ `gdalinfo -json ${srcPath}`;
const bands = JSON.parse(stdout).bands.map((band) => band.band);
const answers = await inquirer.prompt([
{
type: "list",
name: "band",
message: "Select raster band to import",
choices: bands.map((band) => ({
value: band,
name: band,
})),
},
{
type: "list",
name: "measurementType",
message: "What type of measurement is used for this raster data?",
choices: [
{
value: "quantitative",
name: "Quantitative - cell value (number) represents a measurement of a single thing",
},
{
value: "categorical",
name: "Categorical - cell value (number) represents a category the cell is assigned to",
},
],
},
]);
const noDataValue = JSON.parse(stdout).bands.find((b) => b.band === answers.band).noDataValue;
return {
...answers,
formats: ["tif"],
noDataValue: Number.isNaN(noDataValue) ? -9999 : noDataValue,
};
}
//# sourceMappingURL=importData.js.map