diff --git a/packages/api/src/spatialUploads/index.ts b/packages/api/src/spatialUploads/index.ts index 67e8d6a4..baa660ea 100644 --- a/packages/api/src/spatialUploads/index.ts +++ b/packages/api/src/spatialUploads/index.ts @@ -4,6 +4,7 @@ import { customAlphabet } from "nanoid"; import { GeoJsonGeometryTypes } from "geojson"; import { GeostatsLayer, + GeostatsMetadata, RasterInfo, SuggestedRasterPresentation, isRasterInfo, @@ -195,7 +196,9 @@ export async function createDBRecordsForProcessedLayer( layerCount: 1, }, pmtiles && !isVector ? 512 : null, - conversionTask?.attribution || null, + conversionTask?.attribution || + layer.geostats?.metadata?.attribution || + null, conversionTask?.location || null, Boolean(conversionTask), uploadedBy, @@ -320,7 +323,7 @@ export async function createDBRecordsForProcessedLayer( ` update table_of_contents_items set metadata = $1 where id = $2 `, - [conversionTask.metadata || layer.geostats?.metadata, tocItem.id] + [conversionTask.metadata || layer.geostats?.metadata?.doc, tocItem.id] ); } @@ -380,7 +383,7 @@ export async function createDBRecordsForProcessedLayer( [ projectId, nanoId(), - layer.name.replace("_", " "), + layer.geostats?.metadata?.title || layer.name.replace("_", " "), false, layer.bounds, dataLayerId, @@ -388,7 +391,7 @@ export async function createDBRecordsForProcessedLayer( true, JSON.stringify( conversionTask?.metadata || - layer.geostats?.metadata || { + layer.geostats?.metadata?.doc || { type: "doc", content: [ { diff --git a/packages/geostats-types/lib/index.d.ts b/packages/geostats-types/lib/index.d.ts index e994dfbb..47cd31a5 100644 --- a/packages/geostats-types/lib/index.d.ts +++ b/packages/geostats-types/lib/index.d.ts @@ -2,18 +2,18 @@ import { GeoJsonGeometryTypes } from "geojson"; /** * Attribute type as translated to a javacsript type */ -export declare type GeostatsAttributeType = "string" | "number" | "boolean" | "null" | "mixed" | "object" | "array"; +export type GeostatsAttributeType = "string" | "number" | "boolean" | "null" | "mixed" | "object" | "array"; /** * A bucket is a tuple of [break, count]. Each bucket has a count of the number * of features between the break and the next break. The last bucket will have * a null count. */ -export declare type Bucket = [number, number | null]; +export type Bucket = [number, number | null]; /** * A set of buckets for a given number of breaks. This way cartography * interfaces can give the user an option to choose the number of breaks */ -export declare type Buckets = { +export type Buckets = { [numBreaks: number]: Bucket[]; }; export interface BaseGeostatsAttribute { @@ -74,12 +74,31 @@ export interface NumericGeostatsAttribute extends BaseGeostatsAttribute { stdev: number; }; } -export declare type GeostatsAttribute = BaseGeostatsAttribute | NumericGeostatsAttribute; -export declare type LegacyGeostatsAttribute = Omit & { +export type GeostatsAttribute = BaseGeostatsAttribute | NumericGeostatsAttribute; +export type LegacyGeostatsAttribute = Omit & { values: (string | number | boolean | null)[]; quantiles?: number[]; }; export declare function isNumericGeostatsAttribute(attr: GeostatsAttribute): attr is NumericGeostatsAttribute; +export declare enum MetadataType { + ISO19139 = "ISO19139", + FGDC = "FGDC" +} +export type GeostatsMetadata = { + /** + * metadata for the layer summarized as a prosemirror document + */ + doc: any; + /** + * Attribution for the layer + */ + attribution?: string; + type: MetadataType; + /** + * Suggested title based on the metadata + */ + title?: string; +}; export interface GeostatsLayer { /** * Name for the layer @@ -103,12 +122,9 @@ export interface GeostatsLayer { */ attributes: GeostatsAttribute[]; bounds?: number[]; - /** - * Markdown-formatted metadata for the layer - */ - metadata?: JSON; + metadata?: GeostatsMetadata; } -export declare type LegacyGeostatsLayer = Omit & { +export type LegacyGeostatsLayer = Omit & { attributes: LegacyGeostatsAttribute[]; }; export declare function isLegacyGeostatsLayer(layer: LegacyGeostatsLayer | GeostatsLayer): layer is LegacyGeostatsLayer; @@ -118,12 +134,12 @@ export declare function isLegacyGeostatsAttribute(attr: LegacyGeostatsAttribute * number of features between the break and the next break. The last bucket will * have a null fraction. */ -export declare type RasterBucket = [number, number | null]; +export type RasterBucket = [number, number | null]; /** * A set of buckets for a given number of breaks. This way cartography * interfaces can give the user an option to choose the number of breaks */ -export declare type RasterBuckets = { +export type RasterBuckets = { [numBreaks: number]: RasterBucket[]; }; /** @@ -131,8 +147,8 @@ export declare type RasterBuckets = { * raster value and the color is a CSS color string. Raster values should match * those in the stats.categories array. */ -export declare type ColorTableEntry = [number, string]; -export declare type RasterBandInfo = { +export type ColorTableEntry = [number, string]; +export type RasterBandInfo = { name: string; colorInterpretation: "Red" | "Green" | "Blue" | "Alpha" | "Gray" | string | null; base: number; diff --git a/packages/geostats-types/lib/index.js b/packages/geostats-types/lib/index.js index e24f3af6..84451ad9 100644 --- a/packages/geostats-types/lib/index.js +++ b/packages/geostats-types/lib/index.js @@ -1,10 +1,14 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.isGeostatsLayer = exports.isRasterInfo = exports.SuggestedRasterPresentation = exports.isLegacyGeostatsAttribute = exports.isLegacyGeostatsLayer = exports.isNumericGeostatsAttribute = void 0; +exports.SuggestedRasterPresentation = void 0; +exports.isNumericGeostatsAttribute = isNumericGeostatsAttribute; +exports.isLegacyGeostatsLayer = isLegacyGeostatsLayer; +exports.isLegacyGeostatsAttribute = isLegacyGeostatsAttribute; +exports.isRasterInfo = isRasterInfo; +exports.isGeostatsLayer = isGeostatsLayer; function isNumericGeostatsAttribute(attr) { return attr.type === "number"; } -exports.isNumericGeostatsAttribute = isNumericGeostatsAttribute; function isLegacyGeostatsLayer(layer) { if ("attributesCount" in layer && layer.attributesCount) { return layer.attributes[0].countDistinct === undefined; @@ -13,11 +17,9 @@ function isLegacyGeostatsLayer(layer) { return !("bounds" in layer); } } -exports.isLegacyGeostatsLayer = isLegacyGeostatsLayer; function isLegacyGeostatsAttribute(attr) { return Array.isArray(attr.values); } -exports.isLegacyGeostatsAttribute = isLegacyGeostatsAttribute; /** * SuggestedRasterPresentation is a hint to the client on how to present the * raster data. This can be used to determine the default visualization type for @@ -33,12 +35,10 @@ var SuggestedRasterPresentation; SuggestedRasterPresentation[SuggestedRasterPresentation["categorical"] = 0] = "categorical"; SuggestedRasterPresentation[SuggestedRasterPresentation["continuous"] = 1] = "continuous"; SuggestedRasterPresentation[SuggestedRasterPresentation["rgb"] = 2] = "rgb"; -})(SuggestedRasterPresentation = exports.SuggestedRasterPresentation || (exports.SuggestedRasterPresentation = {})); +})(SuggestedRasterPresentation || (exports.SuggestedRasterPresentation = SuggestedRasterPresentation = {})); function isRasterInfo(info) { return info.bands !== undefined; } -exports.isRasterInfo = isRasterInfo; function isGeostatsLayer(data) { return (!Array.isArray(data) && data.attributes !== undefined); } -exports.isGeostatsLayer = isGeostatsLayer; diff --git a/packages/geostats-types/lib/index.ts b/packages/geostats-types/lib/index.ts index f581bea1..50f226bc 100644 --- a/packages/geostats-types/lib/index.ts +++ b/packages/geostats-types/lib/index.ts @@ -101,6 +101,27 @@ export function isNumericGeostatsAttribute( return attr.type === "number"; } +export declare enum MetadataType { + ISO19139 = "ISO19139", + FGDC = "FGDC", +} + +export type GeostatsMetadata = { + /** + * metadata for the layer summarized as a prosemirror document + */ + doc: any; + /** + * Attribution for the layer + */ + attribution?: string; + type: MetadataType; + /** + * Suggested title based on the metadata + */ + title?: string; +}; + export interface GeostatsLayer { /** * Name for the layer @@ -124,10 +145,7 @@ export interface GeostatsLayer { */ attributes: GeostatsAttribute[]; bounds?: number[]; - /** - * Markdown-formatted metadata for the layer - */ - metadata?: JSON; + metadata?: GeostatsMetadata; } export type LegacyGeostatsLayer = Omit< diff --git a/packages/metadata-parser/dist/fgdcToProsemirror.d.ts b/packages/metadata-parser/dist/fgdcToProsemirror.d.ts index de33342c..1fb70f64 100644 --- a/packages/metadata-parser/dist/fgdcToProsemirror.d.ts +++ b/packages/metadata-parser/dist/fgdcToProsemirror.d.ts @@ -1,6 +1,3 @@ -export declare function fgdcToProseMirror(metadata: any): { - title: any; - doc: any; - attribution: string; -}; -export declare function getAttribution(metadata: any): string; +import { GeostatsMetadata } from "@seasketch/geostats-types"; +export declare function fgdcToProseMirror(metadata: any): GeostatsMetadata; +export declare function getAttribution(metadata: any): string | undefined; diff --git a/packages/metadata-parser/dist/fgdcToProsemirror.js b/packages/metadata-parser/dist/fgdcToProsemirror.js index 9fb3710f..221f0a60 100644 --- a/packages/metadata-parser/dist/fgdcToProsemirror.js +++ b/packages/metadata-parser/dist/fgdcToProsemirror.js @@ -182,6 +182,7 @@ function fgdcToProseMirror(metadata) { title: title, doc: doc, attribution: attribution, + type: "FGDC", }; } function getAttribution(metadata) { @@ -203,5 +204,5 @@ function getAttribution(metadata) { var cntinfo = getFirst(metc === null || metc === void 0 ? void 0 : metc.cntinfo); var cntorgp = getFirst(cntinfo === null || cntinfo === void 0 ? void 0 : cntinfo.cntorgp); var contactOrg = getFirst(cntorgp === null || cntorgp === void 0 ? void 0 : cntorgp.cntorg); - return contactOrg || "Unknown attribution"; + return contactOrg || undefined; } diff --git a/packages/metadata-parser/dist/iso19139ToProseMirror.d.ts b/packages/metadata-parser/dist/iso19139ToProseMirror.d.ts index edbfbd01..ba9ddcef 100644 --- a/packages/metadata-parser/dist/iso19139ToProseMirror.d.ts +++ b/packages/metadata-parser/dist/iso19139ToProseMirror.d.ts @@ -1,6 +1,3 @@ -export declare function iso19139ToProseMirror(metadata: any): { - title: any; - doc: any; - attribution: string | null; -}; -export declare function getAttribution(metadata: any): string | null; +import { GeostatsMetadata } from "@seasketch/geostats-types"; +export declare function iso19139ToProseMirror(metadata: any): GeostatsMetadata; +export declare function getAttribution(metadata: any): string | undefined; diff --git a/packages/metadata-parser/dist/iso19139ToProseMirror.js b/packages/metadata-parser/dist/iso19139ToProseMirror.js index 40911d76..2d20312c 100644 --- a/packages/metadata-parser/dist/iso19139ToProseMirror.js +++ b/packages/metadata-parser/dist/iso19139ToProseMirror.js @@ -218,6 +218,7 @@ function iso19139ToProseMirror(metadata) { title: title, doc: doc, attribution: getAttribution(metadata), + type: "ISO19139", }; } // Get attribution from ISO 19139 metadata @@ -235,7 +236,7 @@ function getAttribution(metadata) { } // Fallback to other contact individual info if available var individual = (_f = (_e = responsibleParty === null || responsibleParty === void 0 ? void 0 : responsibleParty["gmd:individualName"]) === null || _e === void 0 ? void 0 : _e[0]["gco:CharacterString"]) === null || _f === void 0 ? void 0 : _f[0]; - return individual || null; + return individual || undefined; } // Helper functions for parsing contact and constraints function parseContact(contact) { diff --git a/packages/metadata-parser/dist/metadata-parser.d.ts b/packages/metadata-parser/dist/metadata-parser.d.ts index e76467ce..dfe4c8ca 100644 --- a/packages/metadata-parser/dist/metadata-parser.d.ts +++ b/packages/metadata-parser/dist/metadata-parser.d.ts @@ -1,7 +1,3 @@ -export declare enum MetadataType { - ISO19139 = 0, - FGDC = 1 -} export interface Contact { name: string; organization: string; @@ -13,8 +9,4 @@ export interface Contact { country: string; email: string; } -export declare function metadataToProseMirror(xmlString: string): Promise<{ - title: any; - doc: any; - attribution: string | null; -} | null>; +export declare function metadataToProseMirror(xmlString: string): Promise; diff --git a/packages/metadata-parser/dist/metadata-parser.js b/packages/metadata-parser/dist/metadata-parser.js index bc042064..0a2453ad 100644 --- a/packages/metadata-parser/dist/metadata-parser.js +++ b/packages/metadata-parser/dist/metadata-parser.js @@ -36,17 +36,11 @@ var __generator = (this && this.__generator) || function (thisArg, body) { } }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.MetadataType = void 0; exports.metadataToProseMirror = metadataToProseMirror; // @ts-ignore var xml2js_1 = require("xml2js"); var fgdcToProsemirror_1 = require("./fgdcToProsemirror"); var iso19139ToProseMirror_1 = require("./iso19139ToProseMirror"); -var MetadataType; -(function (MetadataType) { - MetadataType[MetadataType["ISO19139"] = 0] = "ISO19139"; - MetadataType[MetadataType["FGDC"] = 1] = "FGDC"; -})(MetadataType || (exports.MetadataType = MetadataType = {})); function metadataToProseMirror(xmlString) { return __awaiter(this, void 0, void 0, function () { var data, error_1; diff --git a/packages/metadata-parser/package.json b/packages/metadata-parser/package.json index 9cb1d283..c86f5dd2 100644 --- a/packages/metadata-parser/package.json +++ b/packages/metadata-parser/package.json @@ -26,6 +26,7 @@ "url": "https://github.com/seasketch/next/issues" }, "dependencies": { + "@seasketch/geostats-types": "^1.0.0", "@types/xml2js": "^0.4.14", "xml2js": "^0.6.2" }, diff --git a/packages/metadata-parser/src/fgdcToProsemirror.ts b/packages/metadata-parser/src/fgdcToProsemirror.ts index 0c9f750b..9eecac5e 100644 --- a/packages/metadata-parser/src/fgdcToProsemirror.ts +++ b/packages/metadata-parser/src/fgdcToProsemirror.ts @@ -1,3 +1,4 @@ +import { GeostatsMetadata } from "@seasketch/geostats-types"; import { createParagraphNode } from "./createParagraphNode"; // Helper function to safely access arrays @@ -218,10 +219,11 @@ export function fgdcToProseMirror(metadata: any) { title, doc, attribution, - }; + type: "FGDC", + } as GeostatsMetadata; } -export function getAttribution(metadata: any): string { +export function getAttribution(metadata: any): string | undefined { const getFirst = (value: any) => Array.isArray(value) && value.length > 0 ? value[0] : ""; @@ -240,5 +242,5 @@ export function getAttribution(metadata: any): string { const cntinfo = getFirst(metc?.cntinfo); const cntorgp = getFirst(cntinfo?.cntorgp); const contactOrg = getFirst(cntorgp?.cntorg); - return contactOrg || "Unknown attribution"; + return contactOrg || undefined; } diff --git a/packages/metadata-parser/src/iso19139ToProseMirror.ts b/packages/metadata-parser/src/iso19139ToProseMirror.ts index 9b7ddf01..4a36b337 100644 --- a/packages/metadata-parser/src/iso19139ToProseMirror.ts +++ b/packages/metadata-parser/src/iso19139ToProseMirror.ts @@ -1,3 +1,4 @@ +import { GeostatsMetadata } from "@seasketch/geostats-types"; import { createParagraphNode } from "./createParagraphNode"; // Helper function to safely access arrays @@ -302,11 +303,12 @@ export function iso19139ToProseMirror(metadata: any) { title, doc, attribution: getAttribution(metadata), - }; + type: "ISO19139", + } as GeostatsMetadata; } // Get attribution from ISO 19139 metadata -export function getAttribution(metadata: any): string | null { +export function getAttribution(metadata: any): string | undefined { if ("gmd:MD_Metadata" in metadata) { metadata = metadata["gmd:MD_Metadata"]; } @@ -323,7 +325,7 @@ export function getAttribution(metadata: any): string | null { // Fallback to other contact individual info if available const individual = responsibleParty?.["gmd:individualName"]?.[0]["gco:CharacterString"]?.[0]; - return individual || null; + return individual || undefined; } // Helper functions for parsing contact and constraints diff --git a/packages/metadata-parser/src/metadata-parser.ts b/packages/metadata-parser/src/metadata-parser.ts index 2b02250d..08db767b 100644 --- a/packages/metadata-parser/src/metadata-parser.ts +++ b/packages/metadata-parser/src/metadata-parser.ts @@ -3,11 +3,6 @@ import { parseStringPromise } from "xml2js"; import { fgdcToProseMirror } from "./fgdcToProsemirror"; import { iso19139ToProseMirror } from "./iso19139ToProseMirror"; -export enum MetadataType { - ISO19139, - FGDC, -} - export interface Contact { name: string; organization: string; diff --git a/packages/spatial-uploads-handler/package.json b/packages/spatial-uploads-handler/package.json index f41eb325..be76c77d 100644 --- a/packages/spatial-uploads-handler/package.json +++ b/packages/spatial-uploads-handler/package.json @@ -42,6 +42,7 @@ "@babel/preset-typescript": "^7.24.1", "@mapbox/mbtiles": "^0.12.1", "@seasketch/geostats-types": "^1.0.0", + "@seasketch/metadata-parser": "^1.0.0", "@slack/web-api": "^6.7.2", "@turf/bbox": "^6.5.0", "@types/bytes": "^3.1.1", diff --git a/packages/spatial-uploads-handler/src/iso19139ToMarkdown.ts b/packages/spatial-uploads-handler/src/iso19139ToMarkdown.ts deleted file mode 100644 index 727947e6..00000000 --- a/packages/spatial-uploads-handler/src/iso19139ToMarkdown.ts +++ /dev/null @@ -1,267 +0,0 @@ -import { readFileSync } from "fs"; -import { parseStringPromise } from "xml2js"; - -interface Contact { - name: string; - organization: string; - position: string; - phone: string; - address: string; - city: string; - postalCode: string; - country: string; - email: string; -} - -const parseContact = (contact: any): Contact => { - const contactInfo = contact["gmd:contactInfo"]?.[0]["gmd:CI_Contact"]?.[0]; - - return { - name: contact["gmd:individualName"]?.[0]["gco:CharacterString"]?.[0] || "", - organization: - contact["gmd:organisationName"]?.[0]["gco:CharacterString"]?.[0] || "", - position: - contact["gmd:positionName"]?.[0]["gco:CharacterString"]?.[0] || "", - phone: - contactInfo?.["gmd:phone"]?.[0]["gmd:CI_Telephone"]?.[0]["gmd:voice"] - ?.map((p: any) => p["gco:CharacterString"]?.[0]) - .join(", ") || "", - address: - contactInfo?.["gmd:address"]?.[0]["gmd:CI_Address"]?.[0][ - "gmd:deliveryPoint" - ] - ?.map((p: any) => p["gco:CharacterString"]?.[0]) - .join(", ") || "", - city: - contactInfo?.["gmd:address"]?.[0]["gmd:CI_Address"]?.[0]["gmd:city"]?.[0][ - "gco:CharacterString" - ]?.[0] || "", - postalCode: - contactInfo?.["gmd:address"]?.[0]["gmd:CI_Address"]?.[0][ - "gmd:postalCode" - ]?.[0]["gco:CharacterString"]?.[0] || "", - country: - contactInfo?.["gmd:address"]?.[0]["gmd:CI_Address"]?.[0][ - "gmd:country" - ]?.[0]["gco:CharacterString"]?.[0] || "", - email: - contactInfo?.["gmd:address"]?.[0]["gmd:CI_Address"]?.[0][ - "gmd:electronicMailAddress" - ] - ?.map((e: any) => e["gco:CharacterString"]?.[0]) - .join(", ") || "", - }; -}; - -const parseConstraints = (constraints: any): string => { - const useLimitations = - constraints["gmd:useLimitation"] - ?.map((limitation: any) => limitation["gco:CharacterString"][0]) - .join("; ") || ""; - const accessConstraints = - constraints["gmd:accessConstraints"] - ?.map( - (constraint: any) => - constraint["gmd:MD_RestrictionCode"]?.[0]?.["$"]?.["codeListValue"] - ) - .join(", ") || ""; - const useConstraints = - constraints["gmd:useConstraints"] - ?.map( - (constraint: any) => - constraint["gmd:MD_RestrictionCode"]?.[0]?.["$"]?.["codeListValue"] - ) - .join(", ") || ""; - - let constraintsSummary = ""; - - if (useLimitations) { - constraintsSummary += `- **Use Limitations:** ${useLimitations}\n`; - } - if (accessConstraints) { - constraintsSummary += `- **Access Constraints:** ${accessConstraints}\n`; - } - if (useConstraints) { - constraintsSummary += `- **Use Constraints:** ${useConstraints}\n`; - } - - return constraintsSummary; -}; - -const parseTopicCategories = (categories: any): string => { - const topicCategories = - categories - ?.map((category: any) => category["gmd:MD_TopicCategoryCode"]?.[0]) - .join(", ") || ""; - return topicCategories ? `\n**Topic Categories:** ${topicCategories}\n` : ""; -}; - -const parseDataQuality = (dataQualityInfo: any): string => { - const lineage = - dataQualityInfo["gmd:lineage"]?.[0]["gmd:LI_Lineage"]?.[0][ - "gmd:statement" - ]?.[0]["gco:CharacterString"]?.[0] || ""; - const report = dataQualityInfo["gmd:report"]?.[0]; - let dataQuality = ""; - - if (lineage) { - dataQuality += `\n**Lineage:** ${lineage}\n`; - } - - if (report) { - const explanation = - report["gmd:DQ_Element"]?.[0]["gmd:measureDescription"]?.[0][ - "gco:CharacterString" - ]?.[0] || ""; - if (explanation) { - dataQuality += `\n**Quality Report:** ${explanation}\n`; - } - } - - return dataQuality ? `## Data Quality\n${dataQuality}` : ""; -}; - -const generateMarkdown = (metadata: any): string => { - const md: string[] = []; - - // Title - const title = - metadata["gmd:identificationInfo"][0]["gmd:MD_DataIdentification"][0][ - "gmd:citation" - ][0]["gmd:CI_Citation"][0]["gmd:title"][0]["gco:CharacterString"][0]; - md.push(`# ${title}`); - - // Abstract - const abstract = - metadata["gmd:identificationInfo"][0]["gmd:MD_DataIdentification"][0][ - "gmd:abstract" - ][0]["gco:CharacterString"][0]; - md.push(`\n**Abstract:** ${abstract}\n`); - - // Keywords - const keywords = metadata["gmd:identificationInfo"][0][ - "gmd:MD_DataIdentification" - ][0]["gmd:descriptiveKeywords"]?.[0]["gmd:MD_Keywords"][0]["gmd:keyword"] - ?.map((kw: any) => kw["gco:CharacterString"][0]) - .join(", "); - if (keywords) { - md.push(`\n**Keywords:** ${keywords}\n`); - } - - // Topic Categories - const topicCategories = - metadata["gmd:identificationInfo"][0]["gmd:MD_DataIdentification"][0][ - "gmd:topicCategory" - ]; - if (topicCategories) { - md.push(parseTopicCategories(topicCategories)); - } - - // Temporal Information - const temporalExtent = - metadata["gmd:identificationInfo"][0]["gmd:MD_DataIdentification"][0][ - "gmd:extent" - ]?.[0]["gmd:EX_Extent"]?.[0]["gmd:temporalElement"]?.[0][ - "gmd:EX_TemporalExtent" - ]?.[0]["gmd:extent"]?.[0]["gml:TimePeriod"]?.[0]; - if (temporalExtent) { - const begin = temporalExtent["gml:beginPosition"]?.[0]; - const end = temporalExtent["gml:endPosition"]?.[0]; - md.push( - `\n**Temporal Coverage:**\n- **Start Date:** ${begin}\n- **End Date:** ${end}\n` - ); - } - - // Spatial Extent (Bounding Box) - const boundingBox = - metadata["gmd:identificationInfo"][0]["gmd:MD_DataIdentification"][0][ - "gmd:extent" - ]?.[0]["gmd:EX_Extent"]?.[0]["gmd:geographicElement"]?.[0][ - "gmd:EX_GeographicBoundingBox" - ]?.[0]; - if (boundingBox) { - const west = boundingBox["gmd:westBoundLongitude"]?.[0]["gco:Decimal"][0]; - const east = boundingBox["gmd:eastBoundLongitude"]?.[0]["gco:Decimal"][0]; - const south = boundingBox["gmd:southBoundLatitude"]?.[0]["gco:Decimal"][0]; - const north = boundingBox["gmd:northBoundLatitude"]?.[0]["gco:Decimal"][0]; - md.push( - `\n**Spatial Extent (Bounding Box):**\n- **West:** ${west}\n- **East:** ${east}\n- **South:** ${south}\n- **North:** ${north}\n` - ); - } - - // Data Quality Information (including Lineage) - const dataQualityInfo = - metadata["gmd:dataQualityInfo"]?.[0]["gmd:DQ_DataQuality"]?.[0]; - if (dataQualityInfo) { - const dataQualityMarkdown = parseDataQuality(dataQualityInfo); - if (dataQualityMarkdown) { - md.push(dataQualityMarkdown); - } - } - - // Contact Information - const contact = metadata["gmd:contact"][0]["gmd:CI_ResponsibleParty"][0]; - const contactInfo = parseContact(contact); - md.push(`## Contact Information\n`); - md.push(`- **Name:** ${contactInfo.name}`); - md.push(`- **Organization:** ${contactInfo.organization}`); - md.push(`- **Position:** ${contactInfo.position}`); - md.push(`- **Phone:** ${contactInfo.phone}`); - md.push(`- **Address:** ${contactInfo.address}`); - md.push(`- **City:** ${contactInfo.city}`); - md.push(`- **Postal Code:** ${contactInfo.postalCode}`); - md.push(`- **Country:** ${contactInfo.country}`); - md.push(`- **Email:** ${contactInfo.email}`); - - // Resource Constraints - const resourceConstraints = - metadata["gmd:identificationInfo"][0]["gmd:MD_DataIdentification"][0][ - "gmd:resourceConstraints" - ]; - if (resourceConstraints) { - const constraintsMarkdown = resourceConstraints - .map((constraint: any) => - parseConstraints( - constraint["gmd:MD_LegalConstraints"]?.[0] || - constraint["gmd:MD_SecurityConstraints"]?.[0] || - constraint["gmd:MD_Constraints"]?.[0] - ) - ) - .join("\n"); - if (constraintsMarkdown) { - md.push(`\n## Resource Constraints\n${constraintsMarkdown}`); - } - } - - return md.join("\n"); -}; - -const isIso19139Metadata = (xml: any): boolean => { - return xml && xml["gmd:MD_Metadata"]; -}; - -/** - * - * @param xmlFilePath - * @returns - */ -export const iso19139ToMarkdown = async ( - xmlFilePath: string -): Promise => { - try { - const xml = readFileSync(xmlFilePath, "utf-8"); - const result = await parseStringPromise(xml); - const metadata = result["gmd:MD_Metadata"]; - - if (!isIso19139Metadata(result)) { - throw new Error( - "The provided file is not a valid ISO 19139 metadata file." - ); - } - - const markdown = generateMarkdown(metadata); - return markdown; - } catch (error: any) { - throw new Error(`Error processing XML: ${error.message}`); - } -}; diff --git a/packages/spatial-uploads-handler/src/processVectorUpload.ts b/packages/spatial-uploads-handler/src/processVectorUpload.ts index df2e7d06..aa4c2640 100644 --- a/packages/spatial-uploads-handler/src/processVectorUpload.ts +++ b/packages/spatial-uploads-handler/src/processVectorUpload.ts @@ -1,4 +1,8 @@ -import { GeostatsLayer, RasterInfo } from "@seasketch/geostats-types"; +import { + GeostatsLayer, + GeostatsMetadata, + RasterInfo, +} from "@seasketch/geostats-types"; import { MVT_THRESHOLD, ProgressUpdater, @@ -6,11 +10,11 @@ import { SupportedTypes, } from "./handleUpload"; import { parse as parsePath, join as pathJoin } from "path"; -import { statSync } from "fs"; +import { readFileSync, statSync } from "fs"; import { geostatsForVectorLayers } from "./geostatsForVectorLayer"; import { Logger } from "./logger"; -import { iso19139ToMarkdown } from "./iso19139ToMarkdown"; import { defaultMarkdownParser } from "prosemirror-markdown"; +import { metadataToProseMirror } from "@seasketch/metadata-parser"; export default function fromMarkdown(md: string) { return defaultMarkdownParser.parse(md)?.toJSON(); @@ -56,7 +60,7 @@ export async function processVectorUpload(options: { let type: SupportedTypes; let { ext } = parsePath(path); const isZip = ext === ".zip"; - let metadata: string | undefined; + let metadata: GeostatsMetadata | null = null; // Step 1) Unzip if necessary, and assume it is a shapefile if (isZip) { @@ -118,12 +122,11 @@ export async function processVectorUpload(options: { try { if (xmlPath) { try { - metadata = await iso19139ToMarkdown(xmlPath.trim()); - if (metadata && metadata.length) { - console.log(metadata); - } + console.log("xml path", xmlPath.trim()); + const data = readFileSync(xmlPath.trim(), "utf8"); + metadata = await metadataToProseMirror(data); } catch (e) { - console.error("Problem parsing ISO19139 metadata"); + console.error("Problem parsing xml metadata"); console.error(e); } } @@ -207,8 +210,7 @@ export async function processVectorUpload(options: { const stats = await geostatsForVectorLayers(normalizedVectorPath); if (metadata) { - stats[0].metadata = fromMarkdown(metadata); - console.log(stats[0].metadata); + stats[0].metadata = metadata; } // Only convert to GeoJSON if the dataset is small. Otherwise we can convert // from the normalized fgb dynamically if someone wants to download it as