One of the great promises of Linked Data is the ability to use common identifiers to run queries that pull in data elements from multiple data sources, something we might think of as akin to a distributed or federated JOIN.
In Federating SPARQL Queries Across Government Linked Data and Tell Me About Hampshire – Linking Government Data using SPARQL federation 2, the Ordnance Survey's Jon Goodwin describes how to run SPARQL queries across multiple sources. This notebook draws heavily on those articles.
#Import the necessary packages
from SPARQLWrapper import SPARQLWrapper, JSON
#Add some helper functions
def runQuery(endpoint,prefix,q):
''' Run a SPARQL query with a declared prefix over a specified endpoint '''
sparql = SPARQLWrapper(endpoint)
sparql.setQuery(prefix+q)
sparql.setReturnFormat(JSON)
return sparql.query().convert()
import pandas as pd
def dict2df(results):
''' Hack a function to flatten the SPARQL query results and return the column values '''
data=[]
for result in results["results"]["bindings"]:
tmp={}
for el in result:
tmp[el]=result[el]['value']
data.append(tmp)
df = pd.DataFrame(data)
return df
def dfResults(endpoint,prefix,q):
''' Generate a data frame containing the results of running
a SPARQL query with a declared prefix over a specified endpoint '''
return dict2df( runQuery( endpoint, prefix, q ) )
def printQuery(results,limit=''):
''' Print the results from the SPARQL query '''
resdata=results["results"]["bindings"]
if limit!='': resdata=results["results"]["bindings"][:limit]
for result in resdata:
for ans in result:
print('{0}: {1}'.format(ans,result[ans]['value']))
print()
def printRunQuery(endpoint,prefix,q,limit=''):
''' Print the results from the SPARQL query '''
results=runQuery(endpoint,prefix,q)
printQuery(results,limit)
We are going to make use of several endpoints in this demonstration, so we need to declare each of them separately.
endpoint_envAgency='http://environment.data.gov.uk/sparql/bwq/query'
Let's try a simple query from the Environment Agency's Bathing Water Linked Data store to see what districts are covered.
prefix='''
'''
q='''
SELECT ?x ?name ?district
WHERE {
?x a <http://environment.data.gov.uk/def/bathing-water/BathingWater> .
?x <http://www.w3.org/2000/01/rdf-schema#label> ?name .
?x <http://statistics.data.gov.uk/def/administrative-geography/district> ?district .}
'''
df=dfResults(endpoint_envAgency,prefix,q)
df[:5]
district | name | x | |
---|---|---|---|
0 | http://statistics.data.gov.uk/id/statistical-g... | Ringstead Bay | http://environment.data.gov.uk/id/bathing-wate... |
1 | http://data.ordnancesurvey.co.uk/id/7000000000... | Ringstead Bay | http://environment.data.gov.uk/id/bathing-wate... |
2 | http://data.ordnancesurvey.co.uk/id/7000000000... | Wembury | http://environment.data.gov.uk/id/bathing-wate... |
3 | http://statistics.data.gov.uk/id/statistical-g... | Wembury | http://environment.data.gov.uk/id/bathing-wate... |
4 | http://data.ordnancesurvey.co.uk/id/7000000000... | Cawsand | http://environment.data.gov.uk/id/bathing-wate... |
Notice that the district codes include identifiers that are Ordnance Survey identifiers...
We can also query by name.
prefix='''
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX admingeo: <http://statistics.data.gov.uk/def/administrative-geography/>
'''
q='''
SELECT ?x ?name ?district
WHERE {
#?x rdfs:label "Selsey" .
?x a <http://environment.data.gov.uk/def/bathing-water/BathingWater> .
?x rdfs:label ?name .
?x admingeo:district ?district .
}
'''
printRunQuery(endpoint_envAgency,prefix,q,3)
x: http://environment.data.gov.uk/id/bathing-water/ukk2206-20300 district: http://statistics.data.gov.uk/id/statistical-geography/E07000052 name: Ringstead Bay x: http://environment.data.gov.uk/id/bathing-water/ukk2206-20300 district: http://data.ordnancesurvey.co.uk/id/7000000000014539 name: Ringstead Bay x: http://environment.data.gov.uk/id/bathing-water/ukk4305-26200 district: http://data.ordnancesurvey.co.uk/id/7000000000022569 name: Wembury
Let's have a quick look at some Ordnance Survey Linked Data.
#endpoint_os='http://data.ordnancesurvey.co.uk/datasets/os-linked-data/apis/sparql'
endpoint_os='http://data.ordnancesurvey.co.uk/datasets/boundary-line/apis/sparql'
The OS identifier http://data.ordnancesurvey.co.uk/id/7000000000041421 corresponds to the South East region of the UK. We can query the Ordnance Survery endpoint to find other administrative regions contained within that area.
prefix='''
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX admingeo: <http://statistics.data.gov.uk/def/administrative-geography/>
PREFIX ossr: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>
'''
q='''
SELECT ?district ?districtname
WHERE {
?district ossr:within <http://data.ordnancesurvey.co.uk/id/7000000000041421> .
?district rdfs:label ?districtname .
}
'''
df=dfResults(endpoint_os,prefix,q)
df[:5]
district | districtname | |
---|---|---|
0 | http://data.ordnancesurvey.co.uk/id/7000000000... | East Sussex |
1 | http://data.ordnancesurvey.co.uk/id/7000000000... | Oxfordshire |
2 | http://data.ordnancesurvey.co.uk/id/7000000000... | Buckinghamshire |
3 | http://data.ordnancesurvey.co.uk/id/7000000000... | Surrey |
4 | http://data.ordnancesurvey.co.uk/id/7000000000... | West Sussex |
#What's the code for the Isle of Wight?
for i in df[df['districtname']=='Isle of Wight']['district']: print(i)
http://data.ordnancesurvey.co.uk/id/7000000000025469 http://data.ordnancesurvey.co.uk/id/7000000000025195
If we look these up separately we see that one code (http://data.ordnancesurvey.co.uk/id/7000000000025195) refers to the Westminster constituency and the other (http://data.ordnancesurvey.co.uk/id/7000000000025469) to the Unitary Authority.
#This suggests we could have asked a more direct question of the Ordnance Survey
prefix='''
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX admingeo: <http://statistics.data.gov.uk/def/administrative-geography/>
PREFIX ossr: <http://data.ordnancesurvey.co.uk/ontology/spatialrelations/>
PREFIX osadmingeo: <http://data.ordnancesurvey.co.uk/ontology/admingeo/>
'''
q='''
SELECT ?district ?districtname ?type
WHERE {
?district rdfs:label "Isle of Wight" ;
rdf:type osadmingeo:UnitaryAuthority .
}
'''
printRunQuery(endpoint_os,prefix,q)
district: http://data.ordnancesurvey.co.uk/id/7000000000025469
#7000000000002625
q='''
SELECT ?district
WHERE {
?district rdfs:label "East Sussex" .
}
'''
printRunQuery(endpoint_os,prefix,q)
district: http://data.ordnancesurvey.co.uk/id/7000000000002625
Let's see if the Environment Agency has any information about bathing water around the coast of East Sussex. One way would be to use the unitary authority identifier for this to anchor the search for areas that declare East Sussex as their administrative district.
q='''
SELECT ?x ?name ?districtname
WHERE {
?x <http://statistics.data.gov.uk/def/administrative-geography/district> <http://data.ordnancesurvey.co.uk/id/7000000000002625> .
?x a <http://environment.data.gov.uk/def/bathing-water/BathingWater> .
?x <http://www.w3.org/2000/01/rdf-schema#label> ?name .
?x <http://statistics.data.gov.uk/def/administrative-geography/district> ?district .
?district <http://www.w3.org/2000/01/rdf-schema#label> ?districtname .
}
ORDER BY ?districtname
'''
runQuery(endpoint_envAgency,prefix,q)
{'results': {'bindings': []}, 'head': {'vars': ['x', 'name', 'districtname']}}
Hmm.. no results...
Note also that tt's probably easier to just query by name, as this example for the Isle of Wight shows.
q='''
SELECT ?x ?name ?districtname
WHERE {
?district <http://www.w3.org/2000/01/rdf-schema#label> 'Isle of Wight' .
?x a <http://environment.data.gov.uk/def/bathing-water/BathingWater> .
?x <http://www.w3.org/2000/01/rdf-schema#label> ?name .
?x <http://statistics.data.gov.uk/def/administrative-geography/district> ?district .
?district <http://www.w3.org/2000/01/rdf-schema#label> ?districtname.
}
ORDER BY ?districtname
'''
printRunQuery(endpoint_envAgency,prefix,q,3)
x: http://environment.data.gov.uk/id/bathing-water/ukj3400-18200 districtname: Isle of Wight name: Bembridge x: http://environment.data.gov.uk/id/bathing-water/ukj3400-17600 districtname: Isle of Wight name: Colwell Bay x: http://environment.data.gov.uk/id/bathing-water/ukj3400-17400 districtname: Isle of Wight name: Compton Bay
It seems that what we might need to do is search the Environment Agency for data about bathing water areas within East Sussex. To do that, we need to "join" queries onto both the Ordnance Survey and Environment Agency endpoints.
q='''
SELECT ?location ?districtname ?name ?sedimentname ?lat ?long
WHERE {
SERVICE <http://data.ordnancesurvey.co.uk/datasets/boundary-line/apis/sparql> {
?area rdfs:label "East Sussex".
?district ossr:within ?area.
?district rdfs:label ?districtname.
}
?location a <http://environment.data.gov.uk/def/bathing-water/BathingWater> .
?location <http://environment.data.gov.uk/def/bathing-water/sedimentTypesPresent> ?sediment .
?location <http://statistics.data.gov.uk/def/administrative-geography/district> ?district .
?location rdfs:label ?name.
?sediment rdfs:label ?sedimentname.
?location <http://location.data.gov.uk/def/ef/SamplingPoint/samplingPoint> ?samplingpoint.
?samplingpoint <http://www.w3.org/2003/01/geo/wgs84_pos#lat> ?lat.
?samplingpoint <http://www.w3.org/2003/01/geo/wgs84_pos#long> ?long.
FILTER(LANG(?sedimentname) = "" || LANGMATCHES(LANG(?sedimentname), "en"))
}
ORDER BY ?districtname
'''
printRunQuery(endpoint_envAgency,prefix,q,5)
location: http://environment.data.gov.uk/id/bathing-water/ukj2201-14500 districtname: Eastbourne sedimentname: sand name: Eastbourne long: 0.287079793834733 lat: 50.7609618329818 location: http://environment.data.gov.uk/id/bathing-water/ukj2201-14500 districtname: Eastbourne sedimentname: shingle name: Eastbourne long: 0.287079793834733 lat: 50.7609618329818 location: http://environment.data.gov.uk/id/bathing-water/ukj2202-14150 districtname: Hastings sedimentname: sand name: St Leonards long: 0.551426690368772 lat: 50.8499165351249 location: http://environment.data.gov.uk/id/bathing-water/ukj2202-14150 districtname: Hastings sedimentname: shingle name: St Leonards long: 0.551426690368772 lat: 50.8499165351249 location: http://environment.data.gov.uk/id/bathing-water/ukj2202-14100 districtname: Hastings sedimentname: sand name: Hastings long: 0.582894228862638 lat: 50.8537211550567
Just by the by, let's plot those points to see where the locations are situated on a map.
#The master version of folium currently has an issue running on Python3
#!pip3 install git+https://github.com/tbicr/folium.git@fixed#folium
import folium
folium.initialize_notebook()
df=dfResults(endpoint_envAgency,prefix,q)
df[:3]
districtname | lat | location | long | name | sedimentname | |
---|---|---|---|---|---|---|
0 | Eastbourne | 50.7609618329818 | http://environment.data.gov.uk/id/bathing-wate... | 0.287079793834733 | Eastbourne | sand |
1 | Eastbourne | 50.7609618329818 | http://environment.data.gov.uk/id/bathing-wate... | 0.287079793834733 | Eastbourne | shingle |
2 | Hastings | 50.8499165351249 | http://environment.data.gov.uk/id/bathing-wate... | 0.551426690368772 | St Leonards | sand |
#Check that the lat and long values are numbers
df.dtypes
districtname object lat object location object long object name object sedimentname object dtype: object
#Cast the lat and long values to floats
df['lat']=df['lat'].astype(float)
df['long']=df['long'].astype(float)
#Find their mean values to centre the map
latMean=df['lat'].mean()
longMean=df['long'].mean()
#Create the map with an appropriate zoom level, and centre it
bathingwater = folium.Map(location=[latMean, longMean], zoom_start=10)
#Iterate through the dataframe, adding each sample point as a marker on the map
for ix,row in df[['name','lat','long','sedimentname']].iterrows():
bathingwater.simple_marker( location=[row['lat'],row['long']], popup=row['name'] )
#Render the map
bathingwater
As you can see, we can blend a variety of tools and techniques to help us make sense of the data and better understand it.
In this section, we'll show how a query similar to the one used in Tell Me About Hampshire – Linking Government Data using SPARQL federation 2 can be pieced together.
The area we'll explore is the Isle of Wight. The following query applied to the Ordnance Survey endpoint gives us a list of administrative regions contained within the Isle of Wight.
q='''
SELECT ?districtname
WHERE {
?iw rdfs:label "Isle of Wight" ;
rdf:type osadmingeo:UnitaryAuthority .
?district ossr:within ?iw .
?district rdfs:label ?districtname.
}
'''
printRunQuery(endpoint_os,prefix,q,5)
districtname: Chale districtname: Chillerton and Gatcombe districtname: Niton and Whitwell districtname: Shorwell districtname: Godshill
The next query shows how to pull in data from Open Data Communities, the Department for Communities and Local Government's (DCLG) Linked Data platfrom. In particular, let's pull back the URL for the website of the local authority for a specficied region and the council's IMD (Index of Multiple Deprivation) rank.
#Open Data Communities
endpoint_odc='http://opendatacommunities.org/sparql'
q='''
SELECT ?councilwebsite ?imdrank ?authority ?authorityname
WHERE {
?iw rdfs:label "Isle of Wight" ;
rdf:type osadmingeo:UnitaryAuthority .
?s <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?iw .
?s <http://opendatacommunities.org/def/IMD#IMD-rank> ?imdrank .
?authority <http://opendatacommunities.org/def/local-government/governs> ?iw .
?authority <http://xmlns.com/foaf/0.1/page> ?councilwebsite .
?authority rdfs:label ?authorityname.
}
'''
printRunQuery(endpoint_odc,prefix,q,5)
councilwebsite: http://www.iwight.com authorityname: Isle of Wight imdrank: 106 authority: http://opendatacommunities.org/id/unitary-authority/isle-of-wight
We can combine those two queries together in a single query, whose execution starts at one of the endpoints, in this case the Ordnance Survey endpoint. The SERVICE
command than executes another query fragment on a remote endpoint, in this case the Open Data Communities endpoint.
q='''
SELECT ?districtname ?councilwebsite ?imdrank ?authority ?authorityname
WHERE {
?iw rdfs:label "Isle of Wight" ;
rdf:type osadmingeo:UnitaryAuthority .
?district ossr:within ?iw .
?district rdfs:label ?districtname.
SERVICE <http://opendatacommunities.org/sparql> {
?s <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?iw .
?s <http://opendatacommunities.org/def/IMD#IMD-rank> ?imdrank .
?authority <http://opendatacommunities.org/def/local-government/governs> ?iw .
?authority <http://xmlns.com/foaf/0.1/page> ?councilwebsite .
?authority rdfs:label ?authorityname.
}
}
'''
printRunQuery(endpoint_os,prefix,q,5)
districtname: Chale councilwebsite: http://www.iwight.com authorityname: Isle of Wight imdrank: 106 authority: http://opendatacommunities.org/id/unitary-authority/isle-of-wight districtname: Chillerton and Gatcombe councilwebsite: http://www.iwight.com authorityname: Isle of Wight imdrank: 106 authority: http://opendatacommunities.org/id/unitary-authority/isle-of-wight districtname: Niton and Whitwell councilwebsite: http://www.iwight.com authorityname: Isle of Wight imdrank: 106 authority: http://opendatacommunities.org/id/unitary-authority/isle-of-wight districtname: Shorwell councilwebsite: http://www.iwight.com authorityname: Isle of Wight imdrank: 106 authority: http://opendatacommunities.org/id/unitary-authority/isle-of-wight districtname: Godshill councilwebsite: http://www.iwight.com authorityname: Isle of Wight imdrank: 106 authority: http://opendatacommunities.org/id/unitary-authority/isle-of-wight
As well as using OS administrative codes, the districts also have Office of National Statistics (ONS) identifiers. For example:
q='''
SELECT ?districtname ?onsdist
WHERE {
?iw rdfs:label "Isle of Wight" ;
rdf:type osadmingeo:UnitaryAuthority .
?district ossr:within ?iw .
?district rdfs:label ?districtname.
?district <http://www.w3.org/2002/07/owl#sameAs> ?onsdist .
}
'''
printRunQuery(endpoint_os,prefix,q,5)
districtname: Chale onsdist: http://statistics.data.gov.uk/id/statistical-geography/E04001299 districtname: Chillerton and Gatcombe onsdist: http://statistics.data.gov.uk/id/statistical-geography/E04001302 districtname: Niton and Whitwell onsdist: http://statistics.data.gov.uk/id/statistical-geography/E04001308 districtname: Shorwell onsdist: http://statistics.data.gov.uk/id/statistical-geography/E04001314 districtname: Godshill onsdist: http://statistics.data.gov.uk/id/statistical-geography/E04001303
Let's see what we can find out about a district from the ONS open Linked Data endpoint.
endpoint_ons='http://statistics.data.gov.uk/sparql'
q='''
SELECT ?districtname ?x ?y
WHERE {
<http://statistics.data.gov.uk/id/statistical-geography/E04001302> ?x ?y ;
rdfs:label ?districtname
}
'''
runQuery(endpoint_ons,prefix,q)
{'results': {'bindings': [{'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type', 'type': 'uri'}, 'y': {'value': 'http://statistics.data.gov.uk/def/statistical-geography', 'type': 'uri'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://www.w3.org/2000/01/rdf-schema#label', 'type': 'uri'}, 'y': {'value': 'E04001302', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://www.w3.org/2004/02/skos/core#notation', 'type': 'uri'}, 'y': {'datatype': 'http://statistics.data.gov.uk/def/statistical-entity', 'value': 'E04001302', 'type': 'typed-literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-geography#officialname', 'type': 'uri'}, 'y': {'value': 'Chillerton and Gatcombe', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/boundary-change/originatingChangeOrder', 'type': 'uri'}, 'y': {'value': '5013/2011', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/boundary-change/originatingChangeOrder', 'type': 'uri'}, 'y': {'value': 'http://legislation.gov.uk/id/uksi/2011/5013', 'type': 'uri'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/boundary-change/changeOrderTitle', 'type': 'uri'}, 'y': {'value': 'The Isle of Wight Council (Reorganisation of Community Governance) Order 2011', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/boundary-change/operativedate', 'type': 'uri'}, 'y': {'value': 'http://reference.data.gov.uk/id/day/2011-06-16', 'type': 'uri'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-geography#status', 'type': 'uri'}, 'y': {'value': 'live', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-geography#parentcode', 'type': 'uri'}, 'y': {'value': 'http://statistics.data.gov.uk/id/statistical-geography/E06000046', 'type': 'uri'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-entity#code', 'type': 'uri'}, 'y': {'value': 'http://statistics.data.gov.uk/id/statistical-entity/E04', 'type': 'uri'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-entity#owner', 'type': 'uri'}, 'y': {'value': 'http://reference.data.gov.uk/id/department/dclg', 'type': 'uri'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-geography#hasExteriorEastNorthPolygon', 'type': 'uri'}, 'y': {'value': '450364.700492105 86148.5997075273 450367.600460108 86000.7996433896 450435.499852171 85978.600347369 450290.800460036 85778.4001551826 450221.399883972 85753.3008911592 450185.599819939 85689.2998670996 450159.800139914 85700.2996751098 450180.599627934 85631.1991310455 450131.399499888 85627.1000590416 450184.699723938 85370.6996748028 450143.800139899 85369.799578802 450165.60007592 85316.7000587526 450136.200011893 85285.999514724 450145.900363902 85227.8998026699 450042.099531805 85163.1000586095 449882.799947657 84943.6998664052 449885.299531659 84633.199514116 449835.299659612 84460.9002499556 449789.39988357 84443.8004739396 449772.700491554 84304.4002818097 449680.100171468 84100.8004096202 449897.70017167 83644.6002171953 450015.09972378 83522.1001210812 450032.499531796 83437.5003130024 450109.699915868 83360.2999289305 450144.5998839 83193.1995127749 450440.099660176 82786.5998323963 450497.000268228 82563.3995761883 450469.199692203 82322.1011439636 450087.100235847 82059.2997357189 449897.29978767 81614.1997033043 449843.30017162 81552.1995752465 449636.299595427 81420.6002151241 449576.399691371 81292.3995110047 449364.000587173 81082.1999588088 449222.399819041 80855.1003105973 449035.400010867 80664.3997664198 448959.800138797 80688.5999584423 449129.200458955 80876.3984866173 449165.800266989 81072.8999908002 449195.500363016 81100.9995748263 449014.100810847 81231.2001509477 448870.599498714 81283.3002469962 448942.19962678 81590.9999592827 449053.200202884 81607.400343298 449081.79949891 81807.7991914847 449077.400394906 81922.2997995913 448933.700426773 82289.8000879335 448753.499978605 82224.9010158731 448606.499658468 82460.9995760929 448441.599818314 82422.2002160569 448466.800458338 82536.6998001635 448435.100490308 82545.6997361719 448404.99898628 82465.3997040971 448343.400266223 82431.2001520653 448161.300298053 82681.4002162983 447994.699593898 82574.9001201991 447783.499593701 82564.2996721892 447747.499849668 82576.5999602006 447721.599817644 82671.3005042889 447556.29959349 82632.3004402525 447252.699977207 82764.3001843755 447122.701129086 82745.499544358 447112.098633076 82959.8002165576 447062.30048903 82963.4999285609 447142.599497104 83452.6002170165 447319.199561269 83393.6004089615 447381.799753327 83385.7995769542 447404.999497349 83417.4002169837 447437.099849379 84248.9998337582 447572.599625505 84606.0000260907 447577.50048951 84880.8999943467 447512.300361449 84977.0996744363 447220.900681178 85221.3001226637 447162.699593123 85352.8001547862 447115.099977079 85525.3001229469 447135.500105098 86089.100187472 447588.89965752 85953.3997073455 447744.299849665 86144.4002835234 447904.799561814 86238.5990676112 448315.400010197 85928.0004113219 448482.100042352 86165.9995155436 448437.799754311 86190.9001235667 448452.800330325 86229.4998036027 448565.60007443 86206.7997715815 448568.099658432 86157.2002835353 448629.79975449 86160.4002835383 448674.800458531 86280.9015316506 448690.599754546 86012.0001554001 448877.999946721 86093.6999954762 448860.999498705 86135.1996435148 448973.99994681 86195.0995475707 449083.899722912 86144.4996115235 449226.199883045 86161.099675539 449361.00026717 86027.8004754148 449437.699915242 86022.3998994099 449519.200075318 85908.5997073038 449686.899531474 85822.4997392236 449774.600011556 85884.9995792818 449885.699915659 85891.7999632881 449821.000523599 85954.2998033464 449855.500107631 85970.7001873617 449728.400203513 86263.2999956341 449812.399947591 86183.70140356 449982.70036375 86130.7001875107 450020.800331785 86128.5999635087 450034.400075798 86185.4995475617 450364.700492105 86148.5997075273', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/statistical-geography#hasExteriorLatLongPolygon', 'type': 'uri'}, 'y': {'value': '50.6728307 -1.2886241 50.6715015 -1.2886031 50.6712960 -1.2876454 50.6695083 -1.2897200 50.6692886 -1.2907054 50.6687161 -1.2912206 50.6688173 -1.2915842 50.6681941 -1.2912992 50.6681615 -1.2919959 50.6658513 -1.2912765 50.6658468 -1.2918552 50.6653674 -1.2915540 50.6650939 -1.2919741 50.6645706 -1.2918447 50.6639968 -1.2933220 50.6620376 -1.2956053 50.6592453 -1.2956117 50.6577003 -1.2963421 50.6575504 -1.2969937 50.6562984 -1.2972486 50.6544755 -1.2985857 50.6503547 -1.2955692 50.6492431 -1.2939252 50.6484809 -1.2936905 50.6477801 -1.2926091 50.6462745 -1.2921381 50.6425929 -1.2880142 50.6405809 -1.2872399 50.6384136 -1.2876658 50.6360833 -1.2931042 50.6320972 -1.2958476 50.6315442 -1.2966194 50.6303785 -1.2995635 50.6292308 -1.3004275 50.6273586 -1.3034582 50.6253285 -1.3054900 50.6236294 -1.3081586 50.6238533 -1.3092241 50.6255278 -1.3068047 50.6272917 -1.3062613 50.6275419 -1.3058377 50.6287279 -1.3083850 50.6292084 -1.3104068 50.6319693 -1.3093541 50.6321075 -1.3077826 50.6339071 -1.3073517 50.6349371 -1.3073988 50.6382538 -1.3093822 50.6376852 -1.3119388 50.6398205 -1.3139864 50.6394853 -1.3163233 50.6405128 -1.3159520 50.6405964 -1.3163991 50.6398768 -1.3168352 50.6395744 -1.3177107 50.6418393 -1.3202532 50.6408953 -1.3226229 50.6408173 -1.3256108 50.6409309 -1.3261183 50.6417846 -1.3264724 50.6414473 -1.3288149 50.6426589 -1.3330914 50.6425004 -1.3349321 50.6444283 -1.3350549 50.6444656 -1.3357587 50.6488572 -1.3345610 50.6483123 -1.3320708 50.6482371 -1.3311864 50.6485194 -1.3308543 50.6559947 -1.3302940 50.6591938 -1.3283315 50.6616654 -1.3282269 50.6625357 -1.3291370 50.6647553 -1.3332285 50.6659424 -1.3340352 50.6674974 -1.3346868 50.6725656 -1.3343265 50.6713085 -1.3279281 50.6730133 -1.3257046 50.6738472 -1.3234212 50.6710204 -1.3176514 50.6731467 -1.3152615 50.6733743 -1.3158851 50.6737201 -1.3156678 50.6735066 -1.3140746 50.6730604 -1.3140457 50.6730841 -1.3131722 50.6741639 -1.3125196 50.6717446 -1.3123313 50.6724635 -1.3096688 50.6728381 -1.3099039 50.6733673 -1.3082970 50.6729030 -1.3067485 50.6730403 -1.3047327 50.6718303 -1.3028430 50.6717752 -1.3017584 50.6707450 -1.3006203 50.6699566 -1.2982590 50.6705111 -1.2970096 50.6705627 -1.2954367 50.6711303 -1.2963438 50.6712748 -1.2958534 50.6739167 -1.2976126 50.6731938 -1.2964347 50.6727027 -1.2940320 50.6726805 -1.2934931 50.6731910 -1.2932930 50.6728307 -1.2886241 ', 'type': 'literal'}}, {'districtname': {'value': 'E04001302', 'type': 'literal'}, 'x': {'value': 'http://statistics.data.gov.uk/def/hierarchy/best-fit#OA', 'type': 'uri'}, 'y': {'value': 'http://statistics.data.gov.uk/id/statistical-geography/E00087356', 'type': 'uri'}}]}, 'head': {'vars': ['districtname', 'x', 'y']}}
We can now add this further element to our compund, federated query, using another SERVICE
command to run this part of the query via the ONS endpoint.
q='''
SELECT ?districtname ?councilwebsite ?imdrank ?authority ?authorityname ?changeorder ?onsdist ?onscode
WHERE {
?iw rdfs:label "Isle of Wight" ;
rdf:type osadmingeo:UnitaryAuthority .
?district ossr:within ?iw .
?district rdfs:label ?districtname.
SERVICE <http://opendatacommunities.org/sparql> {
?s <http://purl.org/linked-data/sdmx/2009/dimension#refArea> ?iw .
?s <http://opendatacommunities.org/def/IMD#IMD-rank> ?imdrank .
?authority <http://opendatacommunities.org/def/local-government/governs> ?iw .
?authority <http://xmlns.com/foaf/0.1/page> ?councilwebsite .
?authority rdfs:label ?authorityname.
}
?district <http://www.w3.org/2002/07/owl#sameAs> ?onsdist
SERVICE <http://statistics.data.gov.uk/sparql> {
?onsdist <http://statistics.data.gov.uk/def/boundary-change/originatingChangeOrder> ?changeorder .
?onsdist <http://statistics.data.gov.uk/def/boundary-change/operativedate> ?opdate ;
<http://www.w3.org/2004/02/skos/core#notation> ?onscode.
FILTER (isURI(?changeorder))
}
}
'''
printRunQuery(endpoint_os,prefix,q,5)
For me, the first time I saw a demonstration of the SERVICE
invocation, the promise of Linked Data started to make more sense...