Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add navPlace property to IIIF manifests for objects with defined coordinates #708

Closed
Tracked by #919
thatbudakguy opened this issue Sep 25, 2023 · 12 comments · Fixed by #752
Closed
Tracked by #919

Add navPlace property to IIIF manifests for objects with defined coordinates #708

thatbudakguy opened this issue Sep 25, 2023 · 12 comments · Fixed by #752

Comments

@thatbudakguy
Copy link
Member

thatbudakguy commented Sep 25, 2023

The IIIF presentation API has finalized its navPlace extension, which allows manifests to describe the geographic extent of objects using geoJSON-LD. There are already many client applications beginning to leverage this functionality for rich presentation of content:

Some scanned maps in the SDR have coordinate information already stored as part of the record. If this information exists and is in lat/long format, we could convert it to geoJSON and serve it as part of the manifest so that the map could be displayed in its geographic context.

PURL generates IIIF manifests (specifically, v3) using the Iiif3PresentationManifest model. This issue involves changes to v3, but not v2. I think we still render and send v2 because v3 doesn't include all the properties that some sul-embed viewers need (?); see #164. You can request v3 specifically via the /[druid]/iiif3/manifest endpoint of PURL.

@thatbudakguy thatbudakguy changed the title Add navPlace property to IIIF manifests for objects with defined bounding boxes Add navPlace property to IIIF manifests for objects with defined coordinates Oct 26, 2023
@thatbudakguy
Copy link
Member Author

Example object: https://purl.stanford.edu/bc592pz8308
Its v3 manifest (https://purl.stanford.edu/bc592pz8308/iiif3/manifest) has:

    {
      "label": {
        "en": [
          "Coverage"
        ]
      },
      "value": {
        "en": [
          "[ca.1:60,000,000].",
          "W 160° --E 20°/N 90° --S 90°"
        ]
      }
    }

The second value can be parsed as a bounding box extending from [-160, 90] to [20, -90]. To represent it as geoJSON, we would create a Polygon with coordinates for each of the four corners, and then repeat the first corner, because the spec says that shapes must "close" themselves and return to their first point. So:

{
  "type": "FeatureCollection",
  "features": [
    {
      "id":"https://example.org/iiif/feature/2",
      "type":"Feature",
      "properties":{},
      "geometry":{
        "type":"Polygon",
        "coordinates":[
          [
            [-160, 90],
            [20, 90],
            [20, -90],
            [-160, -90],
            [-160, 90]
          ]
        ]
      }
    }
  ]
}

Which is this bounding box:

{
  "type": "FeatureCollection",
  "features": [
    {
      "id":"https://example.org/iiif/feature/2",
      "type":"Feature",
      "properties":{},
      "geometry":{
        "type":"Polygon",
        "coordinates":[
          [
            [-160, 90],
            [20, 90],
            [20, -90],
            [-160, -90],
            [-160, 90]
          ]
        ]
      }
    }
  ]
}
Loading

@thatbudakguy
Copy link
Member Author

thatbudakguy commented Oct 26, 2023

Some remaining questions:

  • what other parseable formats occur in the Coverage field?
  • do we need to convert to anything other than geoJSON Point or Polygon?
  • are there cases where individual canvases or sequences have Coverage, as opposed to the manifest as a whole?

Most of these can probably be answered by poking around in the rails console looking for scanned map Purls and inspecting their metadata. Alternatively, you could try to tackle it from the Argo side, maybe.

@justinlittman
Copy link
Contributor

Here's the coordinates from the map items.

report.csv

@justinlittman
Copy link
Contributor

This library looks like it supports parsing DMS: https://github.com/zverok/geo_coord

@thatbudakguy
Copy link
Member Author

That parser looks fairly permissive, which is great. There's a lot of weirdness in this data. In case it becomes useful later, I ended up using this regex to get coordinates out:

/([NESW]) ?(\d+(?:[.]\d+)?)[°⁰*]? ?(?:(\d+)[ʺ"ʹ']?)? ?(?:(\d+)[ʺ"ʹ']?)?/

@justinlittman
Copy link
Contributor

@thatbudakguy Does this sound correct?

  1. Extract coordinates:
    For each mods > subject > cartographics > coordinates, parse to extract north degrees, south degrees, east degrees, west degrees. Ignore if not parsable.

  2. If there are any parsed coordinates:
    Add a navPlace to the manifest:

{
   "navPlace":{
      "id": "<purl>/feature-collection/1",
      "type": "FeatureCollection",
      "features":[]
   }
}
  1. For each parsed coordinates, add a feature to features:
{
      "id":"<pur>/iiif/feature/<index>",
      "type":"Feature",
      "properties":{},
      "geometry":{
        "type":"Polygon",
        "coordinates":[
          [
            [-<west coordinate>, <north coordinate>],
            [<east coordinate>, <north coordinate>],
            [<east coordinate>, -<south coordinate>],
            [-<west coordinate>, -<south coordinate>],
            [-<west coordinate>, <north coordinate>]
          ]
        ]
      }
    }

@justinlittman
Copy link
Contributor

How do the minutes and seconds get translated? For example: 51°39'43

@mapninja
Copy link

DMS is a base 60 system, so use: Decimal Degrees = degrees + (minutes/60) + (seconds/3600)
to get to a single decimal lat or long value

So for your example:
DD= 51 + (39/60) + (43/3600)
DD= 51 + 0.65 + .011944
DD= 51.661944 (which at 6 decimal places is precision of about 1m, at our latitude)

@mapninja
Copy link

W & S values are negative (-)

@thatbudakguy
Copy link
Member Author

I think the geo_coord gem can handle this:

irb(main):005> g = Geo::Coord.parse("51°39′43″ N, 36°13′53″E")
=> #<Geo::Coord 51°39'43"N 36°13'53"E>
irb(main):007> g.to_s(dms: false)
=> "51.661944,36.231389"

@thatbudakguy
Copy link
Member Author

thatbudakguy commented Oct 30, 2023

For each mods > subject > cartographics > coordinates, parse to extract north degrees, south degrees, east degrees, west degrees. Ignore if not parsable.

There's at least some objects that don't follow this format: https://purl.stanford.edu/cs542df3540 has only a single point instead of a bounding box. That should become a geoJSON Point instead of Polygon.

@justinlittman justinlittman self-assigned this Oct 31, 2023
justinlittman added a commit that referenced this issue Oct 31, 2023
justinlittman added a commit that referenced this issue Oct 31, 2023
justinlittman added a commit that referenced this issue Oct 31, 2023
justinlittman added a commit that referenced this issue Oct 31, 2023
@justinlittman
Copy link
Contributor

@mapninja This is deployed to https://sul-purl-stage.stanford.edu/ in case you want to kick the tires.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants