\"John Ruskin (8 February 1819 – 20 January 1900) was an English writer, philosopher, art critic and polymath of the Victorian era. He wrote on subjects as varied as geology, architecture, myth, ornithology, literature, education, botany and political economy.\"\n", "\n", "John Ruskin travelled extensively in Europe and was a prolific artist, creating drawings of paintings whose titles often included place names for the locations depicted. \n", "\n", "#### Artwork Title contains Place Name\n", "The title of the artworks has been recorded in the title field in many of the collection data records , and this has been used as the basis for the reconciliation process shown here.\n", "\n", "#### OpenRefine Tool to Reconcile Data\n", "The place names are reconciled with the Getty Thesaurus of Geographic Names (TGN), using the Open Refine tool.\n", "\n", "#### The Getty Thesaurus of Geographic Names (TGN)\n", "Reconciliation with the Getty Thesaurus of Geographic Names (TGN) has allowed additional information to be associated with the artwork: \n", "- an authoritative global identifier for the geographical location depicted \n", "- geographical coordinates\n", "\n", "#### Input Data Files\n", "\n", "The input files are Linked Art files created with the `01-06-Transform-John-Ruskin` Jupyter notebook.\n", "\n", "\n", "### Further Reading\n", "\n", "- The Getty Thesaurus of Geographic Names® Online (TGN) http://www.getty.edu/research/tools/vocabularies/tgn\n", "- John Ruskin Wikipedia entry https://en.wikipedia.org/wiki/John_Ruskin\n", "\n" ] }, { "cell_type": "markdown", "id": "348d9614", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Reconciliation Process\n", "\n", "1. Create CSV file from Linked Art JSON-LD\n", "2. Identify place name in title\n", "3. Use OpenRefine to reconcile place names\n", "4. Define geolocation representation in Linked Art\n", "5. Add place name and coordinates into Linked Art JSON-LD files" ] }, { "cell_type": "markdown", "id": "aec2095c", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## 1. Create CSV file from Linked Art JSON-LD\n", "\n", "To reconcile the place names in the artwork titles\n", "- create a CSV file from the JSON-LD Linked Art files\n", "- CSV contains `id` and `_label` properties\n", "\n", "The script gets a list of all files in a selected directory using `os.listdir()` and iterates over them.\n", "\n", "- `json.load` is used to deserialize the Linked Art JSONLD file to a Python dictionary object. \n", " - json.loads uses the following conversion table https://docs.python.org/3/library/json.html#json-to-py-table \n", "\n", "Finally, the script uses `csv.DictWriter` \n", "- to create an object that maps the Python dictionary onto output rows. \n", "- `Dictwriter.writeheader()` writes a row with the field names (as specified in the constructor) to the writer’s file object. \n", "- `Dictwriter.writerows()` writes all elements in rows to the writer’s file object.\n", "\n", "\n", "#### Further Reading\n", "- os Python library https://docs.python.org/3/library/os.html\n", "- os.listdir() tutorial https://www.tutorialspoint.com/python/os_listdir.htm\n", "- json Python library https://docs.python.org/3/library/json.html\n", "- csv Python library https://docs.python.org/3/library/csv.html" ] }, { "cell_type": "code", "execution_count": 2, "id": "81e0aaea", "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [], "source": [ "# import relevant Python libraries\n", "\n", "try:\n", " import os\n", "except:\n", " %pip install os\n", " import os\n", "\n", "try:\n", " import json\n", "except:\n", " %pip install json\n", " import json \n", " \n", "import csv\n", "\n", "# list holding a\n", "artworkCSV = []\n", "\n", "# Linked Art JSON-LD file location\n", "artworkFileDir = \"./data/ruskin/output/json/\"\n", "artworkFileList =os.listdir(artworkFileDir)\n", "\n", "# iterate over Linked Art JSON-LD files\n", "for artworkFile in artworkFileList:\n", " # read file and append to \n", " with open( artworkFileDir + artworkFile) as artworkFileContents: \n", " \n", " # create json object `artwork` from file\n", " artworkObjJSON = json.load(artworkFileContents)\n", " \n", " # check for \"_label\" property \n", " if \"_label\" not in artworkObjJSON:\n", " continue\n", " \n", " # append artwork properties to artwork JSON object\n", " artworkCSV.append( { \n", " \"id\": artworkObjJSON[\"id\"], \n", " \"place\" : artworkObjJSON[\"_label\"], \n", " \"place_modified\": \" \", \n", " \"coords\": \" \"\n", " })\n", "\n", "# end loop\n", " \n", " \n", "# create CSV file\n", "artworkCsvFile = \"./data/ruskin/ruskin-places.csv\" # file location\n", "\n", "with open(artworkCsvFile, 'w') as f: \n", " # write column headings\n", " w = csv.DictWriter(f, [\"id\",\"place\",\"place_modified\",\"coords\"])\n", " w.writeheader()\n", " # write rows with artwork properties\n", " w.writerows(artworkCSV)" ] }, { "cell_type": "markdown", "id": "5825b0e1", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Result - CSV File with Place Names\n", "\n", "The contents of the resulting CSV file are shown below for illustration.\n", "\n", "The CSV file is read into a `pandas` dataframe. \n", "
`Pandas` is a fast, powerful, flexible and easy to use open source data analysis and manipulation tool, built on top of the Python programming language.\n", " \n", " \n", "A `pandas dataframe` is a pandas data structure containing \n", "
two-dimensional, size-mutable, potentially heterogeneous tabular data.\n", "\n", "The pandas dataframe allows easy manipulation of two-dimensional tabular data. \n", "\n", "The `IPython` library is also used to display the contents of the CSV file \n", " \n", "#### Further Reading \n", "- pandas https://pandas.pydata.org/\n", "- pandas dataframe https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html\n", "- IPython use in Jupyter notebooks https://coderzcolumn.com/tutorials/python/how-to-display-contents-of-different-types-in-jupyter-notebook-lab" ] }, { "cell_type": "code", "execution_count": 3, "id": "85365962", "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/html": [ "
\n", " | id | \n", "place | \n", "place_modified | \n", "coords | \n", "
---|---|---|---|---|
0 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Engraving of Ruskin's Drawing of the Petal Vau... | \n", "\n", " | \n", " |
1 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Enlarged Study of a Prawn's Rostrum | \n", "\n", " | \n", " |
2 | \n", "https://www.harvardartmuseums.org/collections/... | \n", "Study of a Venetian Capital | \n", "\n", " | \n", " |
3 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Autumnal Cloud filling the Valley of Geneva, t... | \n", "\n", " | \n", " |
4 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Axmouth Landslip from Dolands Farm | \n", "\n", " | \n", " |
... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
274 | \n", "https://collections.ashmolean.org/collection/1... | \n", "The Head of a Kite, from Life | \n", "\n", " | \n", " |
275 | \n", "https://www.harvardartmuseums.org/collections/... | \n", "Part of a Sketch of the Northwest Porch of St.... | \n", "\n", " | \n", " |
276 | \n", "http://www.rijksmuseum.nl/nl/collectie/nl-RP-T... | \n", "Gezicht op S. Anastasia te Verona, over de Adige | \n", "\n", " | \n", " |
277 | \n", "https://collections.ashmolean.org/collection/2... | \n", "Architectural detail: stone bracket | \n", "\n", " | \n", " |
278 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Study of the Marble Inlaying on the Front of t... | \n", "\n", " | \n", " |
279 rows × 4 columns
\n", "\n", " | id | \n", "place | \n", "place_modified | \n", "coords | \n", "
---|---|---|---|---|
0 | \n", "https://www.harvardartmuseums.org/collections/... | \n", "Study of a Venetian Capital | \n", "Venezia | \n", "\n", " |
1 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Autumnal Cloud filling the Valley of Geneva, t... | \n", "Geneva | \n", "\n", " |
2 | \n", "https://www.harvardartmuseums.org/collections/... | \n", "Tom Tower, Christ Church, Oxford | \n", "Oxford | \n", "\n", " |
3 | \n", "https://www.harvardartmuseums.org/collections/... | \n", "Study of a Venetian Capital | \n", "Venezia | \n", "\n", " |
4 | \n", "https://www.tate.org.uk/art/artworks/13033 | \n", "View of Bologna | \n", "Bologna | \n", "\n", " |
... | \n", "... | \n", "... | \n", "... | \n", "... | \n", "
102 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Sketch of the Oak Spray in Mantegna's Fresco o... | \n", "Padua | \n", "\n", " |
103 | \n", "https://www.nga.gov/collection/72870 | \n", "The Garden of San Miniato near Florence | \n", "Florence | \n", "\n", " |
104 | \n", "https://www.harvardartmuseums.org/collections/... | \n", "Part of a Sketch of the Northwest Porch of St.... | \n", "Venezia | \n", "\n", " |
105 | \n", "http://www.rijksmuseum.nl/nl/collectie/nl-RP-T... | \n", "Gezicht op S. Anastasia te Verona, over de Adige | \n", "Verona | \n", "\n", " |
106 | \n", "https://collections.ashmolean.org/collection/1... | \n", "Study of the Marble Inlaying on the Front of t... | \n", "Venezia | \n", "\n", " |
107 rows × 4 columns
\n", "Through rich metadata and links, the Getty Vocabularies provide powerful conduits for knowledge creation, research, and discovery for digital art history and related disciplines.\n", "\n", "TGN is a thesaurus. TGN is not a geographic information system (GIS), although it may be linked to existing major, general-purpose, geographic databases and maps. While most records in TGN include coordinates, these coordinates are approximate and are intended for reference (\"finding purposes\") only (as is true of coordinates in most atlases and other resources, including NGA (formerly NIMA) databases).\n", "\n", "\n", "\n", "\n", "#### Further Reading\n", "\n", "- OpenRefine https://openrefine.org/\n", "- The Getty Thesaurus of Geographic Names® Online (TGN) http://www.getty.edu/research/tools/vocabularies/tgn" ] }, { "cell_type": "code", "execution_count": 6, "id": "fce5be0d", "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "data": { "text/html": [ "
Retrieving geocoordinates from vocab.getty.edu TGN API. Please wait for task to complete.
\"))\n", "\n", "# create dataframe to hold geographical coordinates with columns tng and latlng\n", "dataFrameGeo = pd.DataFrame({}, columns=['tgn', 'latlng'])\n", "\n", "# iterate through reconciled data containing place names and TGN identifiers\n", "for identifier_tgn in dataFrameRuskinPlaces['tgn'].unique():\n", " \n", " # print . to indicate progress\n", " print(\".\", end='')\n", " \n", " #create query string for web service - get tgn id using .split()\n", " query = \"http://vocab.getty.edu/tgn/\" + identifier_tgn.split(\"tgn/\",1)[1] +\"-place.json\"\n", " \n", " # use requests.get() to query TGN web service using TGN identifier to return geo coordinates \n", " resultsJSON = requests.get(query).json()\n", " \n", " # get lat lng from web service query results\n", " for record in resultsJSON:\n", " lat = resultsJSON[record][latprop][0][\"value\"]\n", " lng = resultsJSON[record][lngprop][0][\"value\"]\n", " \n", " # create string for lat lng\n", " latlng = str(lat) + \",\" + str(lng)\n", " \n", " \n", " # append TGN identifier and lat lng to the dataFrameGeo \n", " dataFrameGeo = dataFrameGeo.append(\n", " {\n", " 'tgn': identifier_tgn, \n", " 'latlng': latlng\n", " }, \n", " ignore_index=True)\n", " \n", "\n", "# for illustration display dataFrameGeo with addition of geo coords\n", "display(dataFrameGeo)" ] }, { "cell_type": "markdown", "id": "15c96dc7", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Update CSV File with Geographical Coordinates \n", "\n", "The following code \n", "- merges `dataFrameRuskinPlaces` with `dataFrameGeo` containing the geocoordinates.\n", "- removes `coords` from `dataFrameRuskinPlaces` \n", "- renames `latlng` to `coords` in `dataFrameRuskinPlaces` \n", "- writes `dataFrameRuskinPlaces` to a CSV file" ] }, { "cell_type": "code", "execution_count": null, "id": "107a351d", "metadata": {}, "outputs": [], "source": [ "reconciledRuskinPlacesCoords = \"./data/ruskin/ruskin-places-rec-coords.csv\" \n", "\n", "\n", "# merge dataframe with coords with dataframe from csv\n", "dataFrameRuskinPlaces = dataFrameRuskinPlaces.merge(dataFrameGeo, on='tgn') \n", "\n", "# drop column coords\n", "dataFrameRuskinPlaces = dataFrameRuskinPlaces.drop('coords', 1) # drop column coords\n", "\n", "# rename column latlng to coords\n", "dataFrameRuskinPlaces.rename(columns={'latlng': 'coords'}, inplace=True) # rename column tgn to coords\n", "\n", "# drop rows that have na value in coords column\n", "dataFrameRuskinPlaces.dropna(subset=['coords']) \n", "\n", "# write to CSV file\n", "dataFrameRuskinPlaces.to_csv(reconciledRuskinPlacesCoords, index=False)\n", "\n", "display(HTML(\"Many sorts of artwork depict things that can be pointed out in the artwork. These could be identifiable entities, such as a known Person or Object with a name or identifier, or unidentifiable (perhaps fictional) instances of a class of entity, such as a depiction of a battle but not any particular battle. For example a portrait depicts the person sitting for it, or a sketch of a generic landscape depicts a place even if it's not a particular, known location. The depiction pattern describes what is in the artwork's image.\n", "\n", "
This is modeled using the `represents` property on the VisualItem, which refers to the entity that is being depicted.\n", "\n", "\n", "The following representation will be used for place depicted in Ruskin's artworks:\n", "\n", "\n", "`{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://linked.art/example/object/34\",\n", " \"type\": \"HumanMadeObject\",\n", " \"_label\": \"artwork title including place name\",\n", " \"shows\": [\n", " {\n", " \"type\": \"VisualItem\",\n", " \"represents\": [\n", " {\n", " \"type\": \"Place\",\n", " \"_label\": \"place name\"\n", " }\n", " ]\n", " }\n", " ]}`\n" ] }, { "cell_type": "markdown", "id": "1b781b3c", "metadata": {}, "source": [ "### Linked Art Data Model - Geospatial Approximation\n", "\n", "The Linked Art data model describes how to represent geospatial approximation.\n", "\n", "From https://linked.art/model/place/#geospatial-approximation\n", "\n", "
All recorded locations are approximate to some degree. It may be desirable to capture this approximation separately from the actual place, especially when that approximation is very uncertain. Especially if the place is the exact location of several events, and perhaps an address or other information is known, but not the exact geospatial coordinates.\n", " \n", "\n", "
Secondly, as a place is defined by exactly one definition, but there might be multiple approximations such as a polygon as well as the central point, the real place that an activity occured at can be related to multiple approximate places to capture these different approximations.\n", "\n", "\n", "Example Linked Art representation of geospatial approximation:\n", "\n", "`{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://linked.art/example/place/4\",\n", " \"type\": \"Place\",\n", " \"_label\": \"True Auction House Location\",\n", " \"approximated_by\": [\n", " {\n", " \"type\": \"Place\",\n", " \"_label\": \"Auction House Location Approximation\",\n", " \"defined_by\": \"POINT(-0.0032937526703165 51.515107154846)\"\n", " }\n", " ]\n", "}`\n", "\n", "\n", "### Linked Art Data Model - Depiction of Place with Approximate Location\n", "\n", "Relating the Linked Art model for geospatial approximation to the depiction of places in Ruskin's works, the following representation has been created:\n", "\n", "\n", "`{\n", " \"@context\": \"https://linked.art/ns/v1/linked-art.json\",\n", " \"id\": \"https://linked.art/example/object/34\",\n", " \"type\": \"HumanMadeObject\",\n", " \"_label\": \"artwork title including place name\",\n", " \"shows\": [\n", " {\n", " \"type\": \"VisualItem\",\n", " \"represents\": [\n", " {\n", " \"type\": \"Place\",\n", " \"_label\": \"Lucca\",\n", " \"approximated_by\": [\n", " {\n", " \"type\": \"Place\",\n", " \"_label\": \"Lucca - Location Approximation\",\n", " \"defined_by\": \"POINT(-0.0032937526703165 51.515107154846)\"\n", " }\n", " ]\n", " }\n", " ]\n", " }\n", " ]}`\n", "\n", "\n", "\n", "\n", "#### Further reading\n", "\n", "- Depiction https://linked.art/model/object/aboutness/#depiction\n", "\n", "- Geospatial approximation https://linked.art/model/place/#geospatial-approximation" ] }, { "cell_type": "markdown", "id": "c292ee0c", "metadata": {}, "source": [ "
Below is a visualisation of the Linked Art JSON-LD representation of geographical coordinates of a place depicted in an artwork. \n", " \n", "Further information\n", "- explore the representation by clicking on nodes\n", "- SVG representation \n", "- uses \n", " - D3.js\n", " - is a modified version of code available in the JSON-LD Playground codebase\n", "\n", "\n", "#### Further Reading\n", "\n", "- d3.js https://d3js.org/\n", "- jsonld-vis https://github.com/science-periodicals/jsonld-vis\n", "- jsonld playground https://json-ld.org/playground and https://json-ld.org/playground/jsonld-vis.js \n" ] }, { "cell_type": "markdown", "id": "687ef899", "metadata": {}, "source": [ "
" ] }, { "cell_type": "code", "execution_count": null, "id": "d1ccb958", "metadata": {}, "outputs": [], "source": [ "from IPython.core.display import Javascript\n", "\n", "code2 = \"var file = './data/examples/geolocation.json';\"\\\n", " \"var selector = '#vis';\" \\\n", " \"visjsonld(file, selector); \" \n", "\n", "with open('./src/js/visld.js', 'r') as _jscript:\n", " code = _jscript.read() + code2\n", "\n", "Javascript(code)" ] }, { "cell_type": "markdown", "id": "1dfa884a", "metadata": {}, "source": [ "## 5. Add Place Name and Coordinates into Linked Art JSON-LD Files\n", "\n", "The final step is to add place names and geocoordinates to the original Linked Art files. \n", "\n", "The updated Linked Art files, including the geocoordinates, will later be used in a storymap visualisation of the artworks of John Ruskin, mapping the artworks to the locations that they depict, using the geocoordinates.\n", "\n", "The `cromulent` Python library is used to create the JSON-LD representation." ] }, { "cell_type": "code", "execution_count": null, "id": "d37dd072", "metadata": {}, "outputs": [], "source": [ "try:\n", " import cromulent \n", "except:\n", " %pip install cromulent\n", " import cromulent\n", " \n", "from cromulent.model import factory\n", "\n", "from cromulent.model import factory, Actor, Production, BeginningOfExistence, EndOfExistence, TimeSpan, Place\n", "from cromulent.model import InformationObject, Phase, VisualItem \n", "from cromulent.vocab import Painting, Drawing,Miniature,add_art_setter, PrimaryName, Name, CollectionSet, instances, Sculpture \n", "from cromulent.vocab import aat_culture_mapping, AccessionNumber, Height, Width, SupportPart, Gallery, MuseumPlace \n", "from cromulent.vocab import BottomPart, Description, RightsStatement, MuseumOrg, Purchase\n", "from cromulent.vocab import Furniture, Mosaic, Photograph, Coin, Vessel, Graphic, Enamel, Embroidery, PhotographPrint\n", "from cromulent.vocab import PhotographAlbum, PhotographBook, PhotographColor, PhotographBW, Negative, Map, Clothing, Furniture\n", "from cromulent.vocab import Sample, Architecture, Armor, Book, DecArts, Implement, Jewelry, Manuscript, SiteInstallation, Text, Print\n", "from cromulent.vocab import TimeBasedMedia, Page, Folio, Folder, Box, Envelope, Binder, Case, FlatfileCabinet\n", "from cromulent.vocab import HumanMadeObject,Tapestry,LocalNumber\n", "from cromulent.vocab import Type,Set\n", "from cromulent.vocab import TimeSpan, Actor, Group, Acquisition, Place\n", "from cromulent.vocab import Production, TimeSpan, Actor\n", "from cromulent.vocab import LinguisticObject,DigitalObject, DigitalService\n", "from cromulent import reader\n", "\n", "try:\n", " import pandas as pd\n", "except:\n", " %pip install pandas\n", " import pandas as pd\n", " \n", "try:\n", " import os\n", "except:\n", " %pip install os\n", " import os\n", " \n", "try:\n", " import json\n", "except:\n", " %pip install json\n", " import json \n", " \n", "artwork = {}\n", "cnt=1\n", "\n", "# directory that will contain updated Ruskin artwork representations including geo coords\n", "storyvisdir = \"data/ruskin/storyvis/json\"\n", "\n", "# file containing reconciled data with coordinates\n", "filecoord = \"./data/ruskin/ruskin-places-rec-coords.csv\" \n", "# open file containing reconciled data with geo coordinates\n", "dataframeGeo = pd.read_csv(filecoord,low_memory=False)\n", "\n", "\n", "# directory containing Rusking artworks represented in Linked Art JSON-LD\n", "ruskindir = \"data/ruskin/output/json\"\n", "file_list=os.listdir(ruskindir)\n", "\n", "# for each linked art json file\n", "for file in file_list:\n", " # open file\n", " with open( ruskindir + \"/\" + file) as json_file:\n", " \n", " # get json object from file object with json.load() https://www.geeksforgeeks.org/json-load-in-python/\n", " artwork = json.load(json_file)\n", " \n", " # if id field is in the id field of data file containing geographical coordinates, add update the file\n", " if artwork[\"id\"] in dataframeGeo[\"id\"].tolist():\n", " \n", " display(HTML(\"