Simple IIIF Discovery - End-Points

A growing number of institutions are beginning to provide linked open data APIs or direct search portals that allow external users to explore their public digital resources. These can offer a wide range of options and can be used as the basis of new collection presentations or even cross collection search options. However, even when they are well documented, it can still take quite a bit of technical knowledge to fully exploit their potential and it can take some time to learn how local data structures or even international standards have been used before specific resources, such as IIIF manifests, can be found. The propose of the Simple IIIF Discovery system is to provide a structure through which simpler more direct access can be provided to IIIF resources.

A Simple IIIF Discovery End-point is a small website or service that is designed to process simple text based queries and return formatted IIIF resources. They need to be able to process a simple GET request using a defined set of variables, carry out a predefined search for either IIIF manifests or info.json files and then reformat the search results to provide a simple defined set of JSON results that can then be used to display the IIIF resources that have been discovered. Once and end-point has been setup to talk to a given institutions API then far less technical savvy users can use systems like Simple Site to create their own IIIF presentations.

End-Point request URL format

Further description of the four possible GET variables can be found in the model diagram.

End-Point results format

Simple IIIF Discovery end-points are required to return results in the form of a JSON document as shown below. Further description of the various variables can be found in the model diagram.

https://research.ng-london.org.uk/discovery/ng/
Default response from the National Gallery Simple IIIF Discovery end-point.

    Example Simple IIIF Discovery End-Points

    The following six example endpoints have been setup to demonstrate the required functionality.

    https://research.ng-london.org.uk/discovery/aic/https://research.ng-london.org.uk/discovery/gbif/https://research.ng-london.org.uk/discovery/nga/https://research.ng-london.org.uk/discovery/smk/https://research.ng-london.org.uk/discovery/va/https://research.ng-london.org.uk/discovery/wellcome/

    Example PHP End-point script

    Simple IIIF Discovery end-points could be created using a range of programming languages, this simplified version on PHP is just included to provide and example of the processes required.

    <?php
    
    $results = array();
    
    if (!isset($_GET["limit"])) {$_GET["limit"] = 25;}
    if ($_GET["limit"] > 100) {$_GET["limit"] = 100;}
    if (!isset($_GET["from"])) {$_GET["from"] = 0;}
    if (!isset($_GET["search"])) {$_GET["search"] = false;}
    if (!isset($_GET["tag"])) {$_GET["tag"] = "ng";}
    
    $configPath = "/var/www/www-discovery/iiif/configs/";
    // Get details of the APIs and data organisation for a given end-point
    $config = getConfig ($_GET["tag"]); 
    
    if ($_GET["search"])
    	{
    	$cResults = getObjectIIIF ($_GET["search"], intval($_GET["limit"]), intval($_GET["from"]));
    
    	$out = array(		
    		"limit" => $cResults[1],
    		"from" => $cResults[2], 
    		"limited" => $cResults[3],
    		"total" => $cResults[4],
    		"search" => $_GET["search"],
    		"results" => $cResults[0],
    		"comment" => $cResults[5],
    		"altIDs" => $cResults[6]
    		);
    	}
    else
    	{
    	$out = array(		
    		"limit" => 25,
    		"from" => 0, 
    		"limited" => false,
    		"total" => false,
    		"search" => "required-search-term",
    		"results" => array("info"=>array(), "manifests"=>array()),
    		"comment" => "This API has been setup to return lists of IIIF manifests or info.json URLs based on a simple keyword search passed via the URL. Available variable include: \"search\" (the keyword of interest), \"limit\" (a simple limit on the total number of manifest to be returned, up to a maximum of 100, default 25), \"from\" (an offset value to facilitate pagination of results in conjunction with a defined \"limit\" value, default = 0) and \"what\" (determining what should be returned, either IIIF manifests or info.json files, valid options are 'manifests' or 'info', default = 'manifests'.",
    		"altIDs" => array()
    		);
    	}
    
    $json = json_encode($out);
    header('Content-Type: application/json');
    header("Access-Control-Allow-Origin: *");
    echo $json;
    exit;	
    
    function getObjectIIIF ($str, $limit=25, $start=0)
    	{
    	global $config;					
    					
    	$limited = false;	
    	$missed = 0;
    	$out = array("info" => array(), "manifests" => array());
    	
    	// These are used to help individual image searches from the image list on the openseadragon viewer pages.
    	$altIDs = array();
    	$str = urlencode($str);
    	
    	// Some APIs work on a simple count of the number objects to start searching from, but other work in page numbers
    	if (isset($config["page"]) and $config["page"])
    		{$start = floor($start/$limit) + 1;}
    	
    	// Default option that both IIIF manifests and info.json files will be returned from the same query.	
    	if (isset($config["api"]) and $config["api"])
    		{
    		$uri = $config["api"][0].$start.$config["api"][1].$limit.$config["api"][2].$str;
    		$arr = getsslJSONfile($uri, true, true); // Function to call JSON data from a given endpoint
    
    		// Function that can extract fields of groups of fields from the returned data
    		$total = getNestedValue ($arr, $config["total"]);
    		if (!$total) {$total = 0;}
    		if ($total > $limit) {$limited = true;}
    	
    		// Function get data identified by field names
    		$results = getResults ($arr, $config["results"]);
    	
    		// Organise the results into the simple lists required.
    		$mdets = formatResults ($results, $config["manifests"]);
    		$out["manifests"] = array_merge($out["manifests"], $mdets[0]);
    		$altIDs = array_merge($altIDs, $mdets[1]);
    
    		$idets = formatResults ($results, $config["info"]);
    		$out["info"] = array_merge($out["info"], $idets[0]);
    		$altIDs = array_merge($altIDs, $idets[1]);
    		}
    	
    	// If separate calls are needed for the the IIIF manifest and info.json files
    	// they can be processed individually	
    	if (
    		isset($config["info"]["api"]) and isset($config["info"]["results"]) and 
    		isset($config["info"]["total"]) and $config["info"]["api"] and 
    		$config["info"]["results"] and $config["info"]["total"])
    		{		
    		$uri = $config["info"]["api"][0].$start.$config["info"]["api"][1].$limit.$config["info"]["api"][2].$str;
    		$arr = getsslJSONfile($uri);
    
    		$total = getNestedValue ($arr, $config["info"]["total"]);
    		if (!$total) {$total = 0;}
    		if ($total > $limit) {$limited = true;}
    	
    		$results = getResults ($arr, $config["info"]["results"]);
    		$idets = formatResults ($results, $config["info"]);
    		$out["info"] = array_merge($out["info"], $idets[0]);
    		$altIDs = array_merge($altIDs, $idets[1]);	
    		}
    
    		// If separate calls are needed for the the IIIF manifest and info.json files
    	// they can be processed individually	
    	if (
    		isset($config["manifests"]["api"]) and isset($config["manifests"]["results"]) and 
    		isset($config["manifests"]["total"]) and $config["manifests"]["api"] and 
    		$config["manifests"]["results"] and $config["manifests"]["total"])
    		{
    		$uri = $config["manifests"]["api"][0].$start.$config["manifests"]["api"][1].$limit.$config["manifests"]["api"][2].$str;
    		$arr = getsslJSONfile($uri);
    		
    		$mtotal = getNestedValue ($arr, $config["manifests"]["total"]);
    		if (!$mtotal) {$mtotal = 0;}
    		// Need to extend system to cope with different totals.
    		if ($mtotal > $total) {$total = $mtotal;}
    		if ($total > $limit)
    			{$limited = true;}
    
    		$results = getResults ($arr, $config["manifests"]["results"]);				
    		$mdets = formatResults ($results, $config["manifests"]);
    		$out["manifests"] = array_merge($out["manifests"], $mdets[0]);
    		$altIDs = array_merge($altIDs, $mdets[1]);
    		}
    
    	if (isset($config["page"]) and $config["page"])
    		{$start = intval(($start - 1) * $limit);	}
    		
    	$comment = "IIIF resources returned from a full-text object search, for <b>$str</b> of the $config[str].";
    	
    	return (array($out, $limit, $start, $limited, $total, $comment, $altIDs));
    	}
    	
    ?>
    A simplified example of a PHP based end-point.

    Example JSON End-point configuration file

    The current example end-points and example search pages, host by the National Gallery, are all automatically generated using a JSON configuration file. An example with some explanations is provided here. To add a new collection to this system one would just need to provide a completed template.

    {
    	"tag": "Short (2-4) str identifying a given collection",
    	"name": "The display name for a given collection",
    	"logo": "A link to an appropriate logo at least fitting within a 150x150px box ",
    	"link": "Institution website link",
    	"docURL": "Link to the documentation related to the API that is being used",
    	"termsURL": "Link to the licence or terms of use for the data & images pulled from the API",
    	"comment": "Short description of a given collection up to about 100 words.",
    	"examples": [
    		"A short list",
    		"of about 4 or 5",
    		"example info.json urls",
    		"to be displayed on the example landing page"],
    	"str": "A simple string to indicate were the data it from: it will be slotted into existing text ending with \"the\", also please omit a final full-stop as this will also be added automatically.",
    	"total": ["An array of", "field names", "that lead to the", "total number of hits"],
    	"results": [["An", "array", "of"], ["arrays", "of", "field", "names"], ["that", "lead", "to", "results"]],
    	"page": "Boolean true/false value - does the API orgniase the results by page number or a simple start number",
    	"api": ["https://API-SEARCH-URI?page=", "&limit=", "&anyadditionalvariablestocontrolthesearch&SEARCHVARIABLE="],
    	"info": {
    		"total": "Optional \"info\" specific value - structure the same field type as above",	
    		"api":  "Optional \"info\" specific value - structure the same field type as above",
    		"results":  "Optional \"info\" specific value - structure the same field type as above",
    		"regex": "OPTIONAL REGULAR EXPRESSION STRING TO APPLY TO THE VALUE - such as:(.+[\\\/])full[\\\/].+default.jpg",
    		"regexNo": "OPTIONAL LIST OF INDEXES TO CONTROL WHICH REGEX MATCHES TO CONCATENATE - such as [1]",
    		"valueConditionField": "OPTIONAL CONDITION FIELD WHEN TO ALLOW SOME RESULTS TO BE SKIPPED - such as [\"locationType\", \"id\"]",
    		"valueConditionValue": "OPTIONAL VALUE FOR THE CONDITION FIELD TO MATCH - such as iiif-presentation",
    		"value": ["An array of", "field names", "that lead to the", "value"],		
    		"url": "Optional URL prefix used to build the info.json url",
    		"suffix": "/info.json (or simpler optional suffix to complete the URL)",
    		"altID": ["Optional field name used to help the searching for individual images"]
    	},
    	"manifests": {
    		"total": "Optional \"manifest\" specific value - structure the same field type as above",	
    		"api":  "Optional \"manifest\" specific value - structure the same field type as above",
    		"results":  "Optional \"manifest\" specific value - structure the same field type as above",
    		"regex": "OPTIONAL REGULAR EXPRESSION STRING TO APPLY TO THE VALUE - such as:(.+[\\\/])content[\\\/]ngaweb[\\\/](.+)",
    		"regexNo": "OPTIONAL LIST OF INDEXES TO CONTROL WHICH REGEX MATCHES TO CONCATENATE - such as [1, 2]",
    		"valueConditionField": "OPTIONAL CONDITION FIELD WHEN TO ALLOW SOME RESULTS TO BE SKIPPED - such as [\"locationType\", \"id\"]",
    		"valueConditionValue": "OPTIONAL VALUE FOR THE CONDITION FIELD TO MATCH - such as iiif-presentation",
    		"value": ["An array of", "field names", "that lead to the", "value"],
    		"url": "Optional URL prefix used to build the IIIF manifest url",
    		"suffix": "/manifest.json  (or simpler suffix to complete the URL)"
    	}
    }
    A commented example of a JSON, end-point, configuration file.