@allmaps/annotation
Version:
Functions for generating and parsing IIIF georeference annotations
174 lines (173 loc) • 5.74 kB
JavaScript
import { GeoreferencedMap1Schema, GeoreferencedMap2Schema, GeoreferencedMaps1Schema, GeoreferencedMaps2Schema } from './schemas/georeferenced-map.js';
import { isGeoreferencedMapsBeforeParse, isGeoreferencedMap2BeforeParse } from './before-parse.js';
import { isGeoreferencedMap2 } from './guards.js';
function generateSvgSelector(georeferencedMap) {
let width;
let height;
let resourceMask;
if (isGeoreferencedMap2(georeferencedMap)) {
width = georeferencedMap.resource.width;
height = georeferencedMap.resource.height;
resourceMask = georeferencedMap.resourceMask;
}
else {
width = georeferencedMap.image.width;
height = georeferencedMap.image.height;
resourceMask = georeferencedMap.pixelMask;
}
let svg = `<svg>`;
if (width && height) {
svg = `<svg width="${width}" height="${height}">`;
}
return {
type: 'SvgSelector',
value: `${svg}<polygon points="${resourceMask
.map((point) => point.join(','))
.join(' ')}" /></svg>`
};
}
function generateSource(georeferencedMap) {
let id;
let type;
let width;
let height;
let partOf;
if (isGeoreferencedMap2(georeferencedMap)) {
if (georeferencedMap.resource.type === 'Canvas') {
// TODO: Don't know why TypeScript complains if I don't do this
const source = {
id: georeferencedMap.resource.id,
type: georeferencedMap.resource.type,
height: georeferencedMap.resource.height,
width: georeferencedMap.resource.width,
partOf: georeferencedMap.resource.partOf
};
return source;
}
else {
id = georeferencedMap.resource.id;
type = georeferencedMap.resource.type;
width = georeferencedMap.resource.width;
height = georeferencedMap.resource.height;
partOf = georeferencedMap.resource.partOf;
}
}
else {
id = georeferencedMap.image.uri;
type = georeferencedMap.image.type;
width = georeferencedMap.image.width;
height = georeferencedMap.image.height;
}
return {
id,
type,
height,
width,
partOf
};
}
function generateDates(georeferencedMap) {
if (isGeoreferencedMap2(georeferencedMap)) {
return {
created: georeferencedMap.created,
modified: georeferencedMap.modified
};
}
}
function generateContext() {
return [
'http://iiif.io/api/extension/georef/1/context.json',
'http://iiif.io/api/presentation/3/context.json'
];
}
function generateFeature(gcp) {
let resourceCoords;
let geoCoords;
if ('resource' in gcp) {
resourceCoords = gcp.resource;
geoCoords = gcp.geo;
}
else {
resourceCoords = gcp.image;
geoCoords = gcp.world;
}
return {
type: 'Feature',
properties: {
resourceCoords
},
geometry: {
type: 'Point',
coordinates: geoCoords
}
};
}
function generateGeoreferenceAnnotation(georeferencedMap) {
const target = {
type: 'SpecificResource',
source: generateSource(georeferencedMap),
selector: generateSvgSelector(georeferencedMap)
};
let resourceCrs;
if ('resourceCrs' in georeferencedMap) {
resourceCrs = georeferencedMap.resourceCrs;
}
const body = {
type: 'FeatureCollection',
transformation: georeferencedMap.transformation,
resourceCrs,
features: georeferencedMap.gcps.map((gcp) => generateFeature(gcp))
};
return {
id: georeferencedMap.id,
type: 'Annotation',
'@context': generateContext(),
...generateDates(georeferencedMap),
motivation: 'georeferencing',
target,
body
};
}
/**
* Generates a Georeference Annotation from a single Georeferenced Map or
* an Annotation Page containing multiple Georeference Annotations from an array of Georeferenced Maps.
* @param mapOrMaps - Single Georeferenced Map, or an array of Georeferenced Maps
* @returns Georeference Annotation or Annotation Page
* @example
* ```js
* import fs from 'fs'
* import { generateAnnotation } from '@allmaps/annotation'
*
* const map = JSON.parse(fs.readFileSync('./examples/map.example.json'))
* const annotation = generateAnnotation(map)
* ```
*/
export function generateAnnotation(mapOrMaps) {
if (isGeoreferencedMapsBeforeParse(mapOrMaps)) {
// Seperate .parse for different versions for better Zod errors
let parsedGeoreferencedMaps;
if (isGeoreferencedMap2BeforeParse(mapOrMaps[0])) {
parsedGeoreferencedMaps = GeoreferencedMaps2Schema.parse(mapOrMaps);
}
else {
parsedGeoreferencedMaps = GeoreferencedMaps1Schema.parse(mapOrMaps);
}
const annotations = parsedGeoreferencedMaps.map((parsedGeoreferencedMap) => generateGeoreferenceAnnotation(parsedGeoreferencedMap));
return {
type: 'AnnotationPage',
'@context': 'http://www.w3.org/ns/anno.jsonld',
items: annotations
};
}
else {
// Seperate .parse for different versions for better Zod errors
let parsedGeoreferencedMap;
if (isGeoreferencedMap2BeforeParse(mapOrMaps)) {
parsedGeoreferencedMap = GeoreferencedMap2Schema.parse(mapOrMaps);
}
else {
parsedGeoreferencedMap = GeoreferencedMap1Schema.parse(mapOrMaps);
}
return generateGeoreferenceAnnotation(parsedGeoreferencedMap);
}
}