UNPKG

@allmaps/annotation

Version:

Functions for generating and parsing IIIF georeference annotations

171 lines (170 loc) 5.95 kB
import { Annotation0Schema, Annotation1Schema, AnnotationPage0Schema, AnnotationPage1Schema } from './schemas/annotation.js'; import { isAnnotationPageBeforeParse, isAnnotation0BeforeParse } from './before-parse.js'; import { isAnnotation1 } from './guards.js'; function parseResource(annotation) { return { id: parseImageId(annotation), ...parseImageDimensions(annotation), type: parseResourceType(annotation), partOf: parsePartOf(annotation) }; } function parseImageId(annotation) { if (isAnnotation1(annotation)) { const source = annotation.target.source; if ('id' in source) { return source.id; } else { return source['@id']; } } else { return annotation.target.service[0]['@id']; } } function parseResourceType(annotation) { if ('service' in annotation.target) { return annotation.target.service[0].type; } else { return annotation.target.source.type; } } function parsePartOf(annotation) { if (isAnnotation1(annotation)) { return annotation.target.source.partOf; } } function parseResourceCoords(properties) { if ('pixelCoords' in properties) { return properties.pixelCoords; } else { return properties.resourceCoords; } } function parseGcps(annotation) { return annotation.body.features.map((gcpFeature) => ({ resource: parseResourceCoords(gcpFeature.properties), geo: gcpFeature.geometry.coordinates })); } function parseDates(annotation) { if (isAnnotation1(annotation)) { return { created: annotation.created, modified: annotation.modified }; } } function parseImageDimensions(annotation) { if (isAnnotation1(annotation)) { return { width: annotation.target.source.width, height: annotation.target.source.height }; } const selector = annotation.target.selector; const svg = selector.value; const widthResult = /width="(?<width>\d+)"/.exec(svg); const heightResult = /height="(?<height>\d+)"/.exec(svg); const width = widthResult?.groups?.width; const height = heightResult?.groups?.height; if (!width || !height) { throw new Error('Could not parse image dimensions'); } return { width: parseInt(width), height: parseInt(height) }; } function parseResourceMask(annotation) { const selector = annotation.target.selector; const svg = selector.value; const result = /points="(?<points>.+)"/.exec(svg); const groups = result?.groups; if (groups && groups.points) { const pointStrings = groups.points.trim().split(/\s+/); // Resource masks are not round-trip: they don't repeat the first point at the end of the list of points. // According to their spec, SVG polygons are not supposed to be round-trip either. // Here we deal with inputs that are round-trip by removing the last point if (pointStrings[0] === pointStrings[pointStrings.length - 1]) { pointStrings.splice(-1); } if (pointStrings.length >= 3) { return pointStrings.map((point) => { const numberStrings = point.split(','); if (numberStrings.length === 2) { return [parseFloat(numberStrings[0]), parseFloat(numberStrings[1])]; } else { throw new Error('Could not parse resource mask'); } }); } else { throw new Error('Could not parse resource mask'); } } else { throw new Error('Could not parse resource mask'); } } function getGeoreferencedMap(annotation) { let resourceCrs; if ('resourceCrs' in annotation.body) { resourceCrs = annotation.body.resourceCrs; } return { '@context': 'https://schemas.allmaps.org/map/2/context.json', type: 'GeoreferencedMap', id: annotation.id, ...parseDates(annotation), resource: parseResource(annotation), gcps: parseGcps(annotation), resourceMask: parseResourceMask(annotation), transformation: annotation.body.transformation, resourceCrs }; } /** * Parses a Georeference Annotation or an Annotation Page containing multiple Georeference Annotations * and returns an array of Georeferenced Maps. * @param {Annotation | AnnotationPage} annotation - Georeference Annotation or Annotation Page containing multiple Georeference Annotations * @returns {Map[]} Array of maps * @example * ```js * import fs from 'fs' * import { parseAnnotation } from '@allmaps/annotation' * * const annotation = JSON.parse(fs.readFileSync('./examples/annotation.example.json')) * const maps = parseAnnotation(annotation) * ``` */ export function parseAnnotation(annotation) { if (isAnnotationPageBeforeParse(annotation)) { // Seperate .parse for different versions for better Zod errors let parsedAnnotationPage; if ('items' in annotation && Array.isArray(annotation.items) && isAnnotation0BeforeParse(annotation.items[0])) { parsedAnnotationPage = AnnotationPage0Schema.parse(annotation); } else { parsedAnnotationPage = AnnotationPage1Schema.parse(annotation); } return parsedAnnotationPage.items.map((parsedAnnotation) => getGeoreferencedMap(parsedAnnotation)); } else { // Seperate .parse for different versions for better Zod errors let parsedAnnotation; if (isAnnotation0BeforeParse(annotation)) { parsedAnnotation = Annotation0Schema.parse(annotation); } else { parsedAnnotation = Annotation1Schema.parse(annotation); } return [getGeoreferencedMap(parsedAnnotation)]; } }