Image API

Route definitions for the IIIF Image API.

Here we define our route definitions for the IIIF Image API as defined in: http://iiif.io/api/image/2.1/

The Image Request URI Syntax will work as the following example illustrate.

{scheme}://{server}{/prefix}/{identifier}/{region}/{size}/{rotation}/{quality}.{format}

e.g. http://www.example.org/image-service/abcd1234/full/full/0/default.jpg

Image base

get/^(\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X)-((\d{4})|(,\d{4})|(\d{4},)|(\d{4},\d{4})))|(\/SNP([a-zA-Z0-9-]*){6}))(\/)?$/i

Redirect to information page.

Handler

 async ctx => {
  let url = ctx.request.url;
  // If the url ends with a slash, cut it of.
  if (url.length - 1 === url.lastIndexOf('/')) {
    url = url.slice(0, -1);
  }
  ctx.status = 302;
  ctx.redirect(`${url}/info.json`);
}

Image information

get/^(\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X)-((\d{4})|(,\d{4})|(\d{4},)|(\d{4},\d{4})))|(\/SNP([a-zA-Z0-9-]*){6}))\/info.json$/i

Full information for the image in the json format.

Handler

 async ctx => {
  try {
    // Fill up the response body with the information.
    ctx.body = await imageApi.getImageInfo(utilsMain.getUrlParams(ctx.request.url).id,
                                           ctx.request.url);
    // We set the content type after the body, otherwise it will be set
    // to application/json automatically.
    ctx.set('content-type', 'application/ld+json; charset=utf-8');
  } catch (error) {
    /* It's possible that an Error is thrown while creating the image data.
       We expect a boom object here. */
    if (error.isBoom) {
      ctx.status = error.output.statusCode;
      ctx.body = error.output.payload.message;
    } else {
      /* istanbul ignore next */
      throw error;
    }
  }
}

Get the image.

get/^(\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X)-((\d{4})|(,\d{4})|(\d{4},)|(\d{4},\d{4})))|(\/SNP([a-zA-Z0-9-]*){6}))\/(full|square|(\d+,\d+,\d+,\d+)|pct:((\d+)(?:\.\d+)?,(\d+)(?:\.\d+)?,(\d+)(?:\.\d+)?,(\d+)(?:\.\d+)?))\/(full|max|(\d+,)|(,\d+)|(pct:\d+)|(\d+,\d+)|(!\d+,\d+))\/(\d{1,3}(?:\.\d+)?|!\d{1,3}(?:\.\d+)?)\/[a-zA-Z]+\.[a-zA-Z]+$/i

Get the image with the given id with the transformations in the path.

Query: Object

Key = defaultType* (tests) in [valids] not in [invalids]EaxmplesDescription
highlightstring [regex([object Object])]
highlightColorstring [regex([object Object])]

Handler

 async ctx => {
  try {
    const urlParams = utilsMain.getUrlParams(ctx.request.url);
    const imageData = await imageApi.getImageData(ctx);
    const imageMeta = await imageApi.getImageMeta(imageData);
    const filename = utilsImage.buildImageFilename(urlParams);
    ctx.set('content-type', `image/${imageMeta.format}`);
    ctx.set('content-disposition', `inline; filename: '${filename}'`);
    ctx.set('content-length', new String(imageData).length);
    ctx.body = imageData;
  } catch (error) {
    /* It's possible that an Error is thrown while creating the image data.
       We expect a boom object here. */
    if (error.isBoom) {
      ctx.status = error.output.statusCode;
      ctx.body = error.output.payload.message;
    } else {
      /* istanbul ignore next */
      throw error;
    }
  }
}

Miscellaneous

Miscellaneous route definitions that are not included in the IIIF Image API.

The server have beside the implementation of the IIIF Image API some extra functionalities.

Server information

get/

A basic description what is this service for and where are more information are provide.

Handler

   async ctx => {
    ctx.body = `This is the NGCS-Chula, an image server that implements the IIIF
Image API Level 2. See <http://iiif.io/api/image/2.1/> for details and
<http://gitlab.sbb.spk-berlin.de/digitalisierte-sammlungen/ngcs-chula> for the
source code and implementation details.`;
  }

Logo

get/^(\/dc|\/zefys|)\/logo(\/)?$/i

Logo image of the SBB.

Handler

 async ctx => {
  const client = utilsClients.getClient(ctx.request.url);
  const imageLogo = client.getImageLogo();
  ctx.set('content-type', 'image/png');
  ctx.set('content-disposition', `inline; filename: ${imageLogo.filename}`);
  ctx.set('content-length', new String(imageLogo.imageData).length);
  ctx.body = imageLogo.imageData;
}

Download images as ZIP.

get/download/zip

Get a ZIP file containing images and metadata of a specified PPN.

Query: Object

Key = defaultType* (tests) in [valids] not in [invalids]EaxmplesDescription
ppnstring * [alphanum(),min(9),max(13)]
rangestring [length(9)]

Handler

 async ctx => {
  try {
    await zip(ctx);
  } catch (error) {
    if (error.isBoom) {
      ctx.status = error.output.statusCode;
      ctx.body = error.output.payload.message;
    } else {
      // istanbul ignore next
      throw error;
    }
  }
}

Download images as PDF.

get/^(\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X)-((\d{4})|(,\d{4})|(\d{4},)|(\d{4},\d{4})))|(\/SNP([a-zA-Z0-9-]*){6})).(pdf)$/

Get a PDF file containing images specified ID.

Handler

 async ctx => {
  try {
    const client = utilsClients.getClient(ctx.request.url);
    let headerContentDisposition = 'attachment';

    try {
      filename = await client.getPdfFilename(ctx.request.url);
      headerContentDisposition += `; filename: "${filename}"`;
    } catch (err) {
      //app.logger.warn(`[ZEFYS][PDF] No method found to build attachment filename`);
    }

    ctx.set('content-type', 'application/pdf');
    ctx.set('content-disposition', headerContentDisposition);
    await client.getPdf(ctx);
  } catch (error) {
    ctx.set('content-type', 'text/plain');
    ctx.remove('content-disposition');
    if (error.isBoom) {
      ctx.status = error.output.statusCode;
      ctx.body = error.output.payload.message;
    } else {
      // istanbul ignore next
      throw error;
    }
  }
}

Download IIIF manifest.

get/^(\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X))|(\/SNP([a-zA-Z0-9-]*){3}))\/manifest$/

Get a JSON file for the specified id.

Handler

 async ctx => {
  try {
    const client = utilsClients.getClient(ctx.request.url);
    const urlParams = utilsMain.getUrlParams(ctx.request.url);
    const filename = `${urlParams.id}.manifest.json`;
    ctx.set('content-type', 'application/ld+json; charset=utf-8');
    ctx.set('content-disposition', `attachment; filename="${filename}"`);
    ctx.body = await client.getIIIFManifest(ctx);
  } catch (error) {
    ctx.set('content-type', 'text/plain');
    ctx.remove('content-disposition');
    if (error.isBoom) {
      ctx.status = error.output.statusCode;
      ctx.body = error.output.payload.message;
    } else {
      // istanbul ignore next
      throw error;
    }
  }
}

Delete images from the cache.

get/^((\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X)-((\d{4})|(,\d{4})|(\d{4},)|(\d{4},\d{4})))|(\/SNP([a-zA-Z0-9-]*){6}))|(\/dc|\/zefys|)((\/\d{8,9}(\d{1}|X))|(\/SNP([a-zA-Z0-9-]*){3}))).delete.cache$/

Can be called for a specific image or for an object.

Handler

 async ctx => {
  try {
    const urlParams = utilsMain.getUrlParams(ctx.request.url);
    const imageId = urlParams.id.replace('.delete.cache', '');
    const app = require('../app');
    utilsImage.deleteImagesFromCache(app.config.cache.directory, imageId);
    ctx.set('content-type', 'application/json; charset=utf-8');
    ctx.body = {"ack": true};
  } catch (error) {
    ctx.set('content-type', 'text/plain');
    ctx.remove('content-disposition');
    if (error.isBoom) {
      ctx.status = error.output.statusCode;
      ctx.body = error.output.payload.message;
    } else {
      // istanbul ignore next
      throw error;
    }
  }
}

Plugins

Route definitions from the plugins.

The server can have extra routes in the extensible plugins.

Logo small

get/^\/dc\/logo-small(\/)?$/i

Small logo image of the SBB.

Handler

 async ctx => {
  const client = utilsClients.getClient(ctx.request.url);
  const imageLogo = client.getImageLogoSmall();
  ctx.set('content-type', 'image/png');
  ctx.set('content-disposition', `inline; filename: ${imageLogo.filename}`);
  ctx.set('content-length', new String(imageLogo.imageData).length);
  ctx.body = imageLogo.imageData;
}

Download METS file as xml.

get/^\/dc\/(PPN|)\d{8,9}(\d{1}|X).mets.xml$/

Get a XML (METS) file for the specified object id.

Handler

async ctx => {
     try {
         const client = utilsClients.getClient(ctx.request.url);
         let filename = ctx.request.url.split('/')[2].split('.')[0];
         ctx.set('content-type', 'application/xml; charset=utf-8');
         ctx.set('content-disposition', `attachment; filename="sbb-mets-${filename}.xml"`);
         ctx.body = await client.getMetsFile(ctx);
     } catch (error) {
         ctx.set('content-type', 'text/plain');
         ctx.remove('content-disposition');
         if (error.isBoom) {
             ctx.status = error.output.statusCode;
             ctx.body = error.output.payload.message;
         } else {
             // istanbul ignore next
             throw error;
         }
     }
 }

Download OCR file as xml.

get/^\/dc\/(PPN|)\d{8,9}(\d{1}|X)-\d{4}.ocr.xml$/

Get a XML (ALTO) file for the specified object id.

Handler

async ctx => {
     try {
         const client = utilsClients.getClient(ctx.request.url);
         let filename = ctx.request.url.split('/')[2].split('.')[0];
         ctx.set('content-type', 'application/xml; charset=utf-8');
         ctx.set('content-disposition', `attachment; filename="sbb-ocr-alto-${filename}.xml"`);
         ctx.body = await client.getOcrFile(ctx);
     } catch (error) {
         ctx.set('content-type', 'text/plain');
         ctx.remove('content-disposition');
         if (error.isBoom) {
             ctx.status = error.output.statusCode;
             ctx.body = error.output.payload.message;
         } else {
             // istanbul ignore next
             throw error;
         }
     }
 }

Download OCR files as zip.

get/^\/dc\/(PPN|)\d{8,9}(\d{1}|X).ocr.zip$/

Get a ZIP file for the specified object id containing all ALTO files of the OCR.

Query: Object

Key = defaultType* (tests) in [valids] not in [invalids]EaxmplesDescription
rangestring [length(9)]

Handler

async ctx => {
     try {
         const client = utilsClients.getClient(ctx.request.url);
         await client.getOcrZip(ctx);
     } catch (error) {
         ctx.set('content-type', 'text/plain');
         ctx.remove('content-disposition');
         if (error.isBoom) {
             ctx.status = error.output.statusCode;
             ctx.body = error.output.payload.message;
         } else {
             // istanbul ignore next
             throw error;
         }
     }
 }

Download OCR files as text.

get/^\/dc\/(PPN|)\d{8,9}(\d{1}|X).ocr.txt$/

Get a TXT file for the specified object id containing all OCR as simple text.

Handler

async ctx => {
     try {
         const client = utilsClients.getClient(ctx.request.url);
         let filename = ctx.request.url.split('/')[2].split('.')[0];
         ctx.set('content-type', 'text/plain; charset=utf-8');
         ctx.set('content-disposition', `attachment; filename="sbb-${filename}.ocr.txt"`);
         ctx.body = await client.getOcrTxt(ctx);
     } catch (error) {
         ctx.set('content-type', 'text/plain');
         ctx.remove('content-disposition');
         if (error.isBoom) {
             ctx.status = error.output.statusCode;
             ctx.body = error.output.payload.message;
         } else {
             // istanbul ignore next
             throw error;
         }
     }
 }

Download XML.

get/^\/zefys((\/\d{8,9}(\d{1}|X)-((\d{4})|(,\d{4})|(\d{4},)|(\d{4},\d{4})))|(\/SNP([a-zA-Z0-9-]*){6})).xml$/

Get a XML file for the specified image (ALTO) or issue (METS).

Handler

async ctx => {
     try {
         const client = utilsClients.getClient(ctx.request.url);
         const filename = ctx.request.url.split('/')[2];
         ctx.set('content-type', 'application/xml; charset=utf-8');
         ctx.set('content-disposition', `attachment; filename: "${filename}"`);
         await client.getXml(ctx);
     } catch (error) {
         ctx.set('content-type', 'text/plain');
         ctx.remove('content-disposition');
         if (error.isBoom) {
             ctx.status = error.output.statusCode;
             ctx.body = error.output.payload.message;
         } else {
             // istanbul ignore next
             throw error;
         }
     }
 }