Tech Blog

Implement a geo-search widget in Primo

Some institutions maintain records in their catalog which are related to a specific geographic location. Records which represent images, manuscripts, or other material which was either composed in or is pertaining to a location is perhaps best displayed on a map. The Primo customization framework allows us to add a widget which shows the relevant records as points on a map.

In this post, we will do the following:

  • Harvest records from Alma which represent images taken in various locations
  • Extract the geographic positioning properties from the images and store them in a database
  • Create a service which exposes the coordinates of the images as GeoJSON
  • Customize our Primo view to show a map with points which link to the records

Harvest records from Alma

In order to extract the geographic metadata from our records, we want to harvest them from Alma. We’ll use Alma’s publishing capabilities to create a new general publishing profile for a set which contains records in my collection and choose to publish the records as OAI. Since we’re interested in images, we’ll configure the publishing profile to enrich the records with digital inventory.

Extracting the GPS properties

Next we’ll build a harvesting script which has the following logic:

  1. Retrieve the records from the OAI link
  2. For each record, look for the representation links identified by a dc:identifier field
  3. Retrieve the JSON version of each representation
  4. Download the first 64kb of each file and extract the GPS coordinates from the Exif data
  5. Save all of the information into a MongoDB as a JSON object

GeoJSON Service

Now that all of our records and their coordinates are stored in our MongoDB, we can prepare a service which returns the information in GeoJSON format. GeoJSON is a standard format used to represent geographic information. It’s supported by a number of mapping programs and can be used as input to our map widget.

The service retrieves all of the records from the database and maps the resulting array into an array of GeoJSON “features”.

  "type": "FeatureCollection",
  "features": [
      "type": "Feature",
      "geometry": {
        "type": "Point",
        "coordinates": [
      "properties": {
        "title": "Spokane 2018",
        "description": "<a href=\"\"><div><strong>Spokane 2018</strong><img src=\"\"><span>Spokane Falls, Spokane, WA</span></div></a>",
        "url": ""

Primo Custom View

Now we can build a Primo customized view which includes a map widget on the homepage. We chose to use the MapBox SDK to build our map. Our customized view includes the following pages:

  • custom.js: Defines the following:
    • an AngularJS component called geo-search which includes the div for our map
    • an AngularJS controller which loads the MapBox SDK and the GeoJSON data from our service, and handles the metadata popup boxes
  • homepage_en.html: Includes the custom geo-search component
  • custom1.css: Adds some styling for our widget

The resulting homepage appears below. You can view a demo of the widget.

We also define a popup which shows some metadata from the records in Alma, and we link the click event to open the delivery viewer. We could also easily link to the full record view in Primo.

Extending this post

This post can be extended to provide additional functionality.

Smart Search

Since we store the geographic data in a MongoDB, we can take advantage of its features to enable smart search. For example, we can store our GeoJSON directly in Mongo, or we can create a geospatial index on our coordinates field as follows:

db.geosearch.createIndex( {gps: "2d"} )

Then we can use geospatial queries to search for records within a particular area, such as:

  gps: {
     $geoWithin: {
        $box: [
          [ 60.4187959,5.368723 ],
          [ 65.4187959,15.368723 ]

Of course we could also search for key terms in the stored metadata as well.

Geographic data from additional sources

When we harvest our records, we’re extracting GPS coordinates from Exif data. We could get coordinates from other sources as well. For example, we could query the Geonames web service with a place name taken from the bibliographic record. The web service returns coordinates which can be stored in our database.

Deploying the Service

The code for this post is available on this Github repository. In addition, we’ve added a “Deploy to Heroku” button which allows you to deploy the harvester and GeoJSON service to the Heroku cloud platform, including a MongoDB instance and a scheduler to schedule the harvester task.

Once you’ve deployed the service, you can run the harvest script for the initial harvesting. From the Heroku web interface, select More–>Run Console.  Then execute the command npm run harvest. (If have you have Heroku CLI installed, you can execute heroku run -a APP_NAME npm run harvest .

$ heroku run npm run harvest
Running npm run harvest on ⬢ alma-primo-geosearch... up, run.9291 (Free)

> alma-primo-geosearch@0.0.1 harvest /app
> LOG_LEVEL=debug node ./harvest/index.js | ./node_modules/.bin/pino-pretty

[1570118146928] INFO  (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Begin harvesting @
[1570118147956] INFO  (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing rep @
[1570118147958] INFO  (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing rep @
[1570118147959] INFO  (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing rep @
[1570118148487] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Retrieved info for rep 12152479120000561
[1570118148487] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing file Clock tower at Riverfront Park, Spokane, WA
[1570118148489] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing file Sunrise at Spokane Falls, Spokane, WA
[1570118148489] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing file Overlooking downtown Spokane from the Centennial Trail, Spokane, WA
[1570118148490] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Processing file Spokane Falls, Spokane, WA
[1570118149535] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Inserting DB entries for rep 12152479240000561
[1570118149593] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Deleting DB entries for rep 12152479510000561
[1570118149596] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Inserting DB entries for rep 12152479510000561
[1570118149598] INFO  (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Harvested 12 reprentations
[1570118149605] DEBUG (23 on b1e18f80-17cf-4315-8046-d3e9988ae0e5): Saved from time 2019-10-03T15:55:46Z

To schedule the harvester to run daily, click on the Scheduler add-on in the Heroku web interface. Click “Create Job”, then select daily. In “Run command”, enter npm run harvest.

2 Replies to “Implement a geo-search widget in Primo”

  1. This is very cool. I imagine another source could be the metadata record itself eg. coordinates from within Dublin Core coverage/spacial tags. This would then support other types of resources too.

    I wonder about the implications of writing back the coordinates into the records themselves.

    Nice work.

Leave a Reply