Skip to content

Commit

Permalink
Merge pull request #60 from UtrechtUniversity/feature/labs
Browse files Browse the repository at this point in the history
Feature/labs
  • Loading branch information
lsamshuijzen authored Jun 5, 2024
2 parents a4334a4 + 2576f12 commit 5ecc32e
Show file tree
Hide file tree
Showing 35 changed files with 3,161 additions and 5 deletions.
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,6 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"

CKAN_API_URL=
CKAN_API_TOKEN=
CKAN_ROOT_URL=
CKAN_ROOT_URL=

FAST_API_TOKEN=
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add vocabulary display names to database, seeding, vocabulary api and exports
- Add new tag field to datapublication schema to work with matched vocabulary terms
- Add child uri to enriched keywords for displaying connections in frontend
- Add new lab data management to msl_api;
- Setup facility/equipment and origanization models as setup in FAST
- Create update function to synch data from fast to msl_api
- Populate facility organization data using ROR identifier as set in FAST
- Add function to ass keywords to facilities based on vocabularies and facility description/title
- Add option to serialise facility data to RDF/turtle for data exange with EPOS

## [1.5.1] - 2024-02-28

Expand Down
257 changes: 257 additions & 0 deletions app/Exports/epos/RegistryExport.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
<?php
namespace App\Exports\epos;

use App\Models\Keyword;
use App\Models\Vocabulary;
use EasyRdf\Graph;
use App\Models\LaboratoryOrganization;
use EasyRdf\Resource;
use EasyRdf;
use EasyRdf\RdfNamespace;
use App\Models\LaboratoryContactPerson;
use App\Models\Laboratory;
use EasyRdf\Literal;
use App\Models\LaboratoryEquipment;

class RegistryExport
{
public $organizations;

public function __construct($organizations) {
$this->organizations = $organizations;
}

public function export($type = 'turtle')
{
$graph = new Graph();

// Set namespaces
RdfNamespace::delete('dc');
RdfNamespace::delete('dcterms');
RdfNamespace::set('dct', 'http://purl.org/dc/terms/');
RdfNamespace::set('epos', 'https://www.epos-eu.org/epos-dcat-ap#');
RdfNamespace::set('locn', 'http://www.w3.org/ns/locn#');
RdfNamespace::set('gsp', 'http://www.opengis.net/ont/geosparql#');
RdfNamespace::set('skos', 'http://www.w3.org/2004/02/skos/core#');

foreach ($this->organizations as $organization) {
// Add organization to graph
$organizationGraph = $graph->resource($organization->external_identifier, 'schema:Organization');
$schemaIdentifier = $graph->newBnode('schema:PropertyValue');
$schemaIdentifier->set('schema:propertyID', "ROR");
$schemaIdentifier->set('schema:value', $organization->external_identifier);
$organizationGraph->set('schema:identifier', $schemaIdentifier);
$organizationGraph->set('schema:legalName', $organization->name);
// add address information
$organizationAddress = $graph->newBnode('schema:PostalAddress');
$organizationAddress->set('schema:addressCountry', $organization->ror_country_code);
$organizationGraph->set('schema:address', $organizationAddress);

$organizationGraph->set('schema:url', Literal::create($organization->ror_website , null, 'xsd:anyURI'));

// Get laboratories belonging to organization
foreach ($organization->laboratories as $laboratory) {
/*
* Skip if no location data is present
*/

if((strlen($laboratory->latitude) == 0) || (strlen($laboratory->longitude) == 0)) {
continue;
}


// Add contactpoint of laboratory to graph
$contactPerson = $laboratory->laboratoryContactPerson;

$contactPointGraph = $graph->resource($this->generateContactPersonURI($contactPerson), 'schema:ContactPoint');
$contactPointGraph->set('schema:email', $contactPerson->email);
$contactPointGraph->set('schema:availableLanguage', 'en');
$contactPointGraph->set('schema:contactType', 'Contact');

// Add Laboratory to graph
$facilityGraph = $graph->resource($this->generateFacilityURI($laboratory), 'epos:Facility');
$facilityGraph->set('dct:identifier', $facilityGraph->getUri());
$facilityGraph->set('dct:title', $laboratory->name);
$facilityGraph->set('dct:description', $this->formatNewlines($laboratory->description));
$facilityGraph->set('dct:type', ['type' => 'uri', 'value' => '<epos:Laboratory>']);
$facilityGraph->set('dcat:theme', ['type' => 'uri', 'value' => $this->getEPOSlabType($laboratory)]);
$facilityGraph->set('dcat:contactPoint', ['type' => 'uri', 'value' => $contactPointGraph->getUri()]);
if((strlen($laboratory->latitude) > 0) && (strlen($laboratory->longitude) > 0)) {
$facilityLocation = $graph->newBNode('dct:Location');
$facilityLocation->add("locn:geometry", Literal::create($this->generateGeonometryString($laboratory), null, 'gsp:wktLiteral'));
$facilityGraph->set('dct:spatial', $facilityLocation);
}

// Add keywords
$facilityGraph->set('dcat:keyword', $laboratory->fast_domain_name);
$facilityKeywords = $laboratory->laboratoryKeywords->pluck('value')->toArray();
$facilityKeywords = array_unique($facilityKeywords);

foreach ($facilityKeywords as $facilityKeyword) {
$facilityGraph->add('dcat:keyword', $facilityKeyword);
}

// optional
if(strlen($laboratory->website) > 0) {
$facilityGraph->add(
'foaf:page', Literal::create($laboratory->website, null, 'xsd:anyURI')
);
}

// Add facility to organization owns
if ($organizationGraph->hasProperty('schema:owns')) {
$organizationGraph->add('schema:owns', $facilityGraph);
} else {
$organizationGraph->set('schema:owns', $facilityGraph);
}

// Add equipment belonging to lab
foreach ($laboratory->laboratoryEquipment as $equipment) {
$equipmentGraph = $graph->resource($this->generateEquipmentURI($equipment), 'epos:Equipment');
$equipmentGraph->set('schema:identifier', $this->generateEquipmentURI($equipment));
$equipmentGraph->set('schema:name', $equipment->group_name);
$equipmentGraph->set('schema:description', $this->formatNewlines($equipment->description));

$equipmentGraph->set('dct:type', ['type' => 'uri', 'value' => $this->getEquipmentType($equipment)]);

$equipmentGraph->set('dct:isPartOf', ['type' => 'uri', 'value' => $facilityGraph->getUri()]);
$equipmentGraph->add('dcat:keyword', $equipment->category_name);
$equipmentGraph->add('dcat:keyword', $equipment->type_name);

$equipmentLocation = $graph->newBNode('dct:Location');
$equipmentLocation->add("locn:geometry", Literal::create($this->generateGeonometryString($laboratory), null, 'gsp:wktLiteral'));
$equipmentGraph->set('dct:spatial', $equipmentLocation);

}

}

}



// concepts

// lab concepts

$facilityTypeConcept = $graph->resource('epos:Facility_Type', 'skos:ConceptScheme');
$facilityTypeConcept->set('dct:description', 'Facility type');
$facilityTypeConcept->set('dct:title', 'Facility type');

$laboratoryConcept = $graph->resource('epos:Laboratory', 'skos:Concept');
$laboratoryConcept->set('skos:definition', 'Laboratory');
$laboratoryConcept->set('skos:inScheme', ['type' => 'uri', 'value' => '<epos:Facility_Type>']);
$laboratoryConcept->set('skos:prefLabel', 'Laboratory');


// equipment concepts

$equipmentConcept = $graph->resource('epos:Equipment_Type', 'skos:ConceptScheme');
$equipmentConcept->set('dct:description', 'Equipment type');
$equipmentConcept->set('dct:title', 'Equipment type');

$equipmentDefinition = $graph->resource('epos:Atomic_Force_Microscopy_definition', 'skos:Concept');
$equipmentDefinition->set('skos:definition', 'Atomic Force Microscopy');
$equipmentDefinition->set('skos:inScheme', ['type' => 'uri', 'value' => '<epos:Equipment_Type>']);
$equipmentDefinition->set('skos:prefLabel', 'Atomic Force Microscopy');

$equipmentDefinition = $graph->resource('epos:Electron_Microscopy_definition', 'skos:Concept');
$equipmentDefinition->set('skos:definition', 'Electron Microscopy');
$equipmentDefinition->set('skos:inScheme', ['type' => 'uri', 'value' => '<epos:Equipment_Type>']);
$equipmentDefinition->set('skos:prefLabel', 'Electron Microscopy');

$equipmentDefinition = $graph->resource('epos:Electron_Probe_Micro_Analyzer_definition', 'skos:Concept');
$equipmentDefinition->set('skos:definition', 'Electron Probe Micro Analyzer');
$equipmentDefinition->set('skos:inScheme', ['type' => 'uri', 'value' => '<epos:Equipment_Type>']);
$equipmentDefinition->set('skos:prefLabel', 'Electron Probe Micro Analyzer');

$equipmentDefinition = $graph->resource('epos:Mass_Spectrometer_definition', 'skos:Concept');
$equipmentDefinition->set('skos:definition', 'Mass Spectrometer');
$equipmentDefinition->set('skos:inScheme', ['type' => 'uri', 'value' => '<epos:Equipment_Type>']);
$equipmentDefinition->set('skos:prefLabel', 'Mass Spectrometer');

$equipmentDefinition = $graph->resource('epos:X-Ray_Tomography_definition', 'skos:Concept');
$equipmentDefinition->set('skos:definition', 'X-Ray Tomography');
$equipmentDefinition->set('skos:inScheme', ['type' => 'uri', 'value' => '<epos:Equipment_Type>']);
$equipmentDefinition->set('skos:prefLabel', 'X-Ray Tomography');



return $graph->serialise($type);
}

private function generateContactPersonURI(LaboratoryContactPerson $contactPerson) {
// Return orcid if available otherwise generate custom uri
if(strlen($contactPerson->orcid) > 5) {
return 'http://orcid.org/' . $contactPerson->orcid . '/contactperson';
} else {
return 'https:/epos-msl/contactperson/' . $contactPerson->fast_id;
}
}

private function generateFacilityURI(Laboratory $laboratory) {
return 'https:/epos-msl/facility/' . $laboratory->fast_id;
}

private function generateEquipmentURI(LaboratoryEquipment $equipment) {
return 'https:/epos-msl/equipment/' . $equipment->fast_id;
}

private function generateGeonometryString(Laboratory $laboratory) {
return "POINT(" . str_replace(',', '.', $laboratory->longitude) . " " . str_replace(',', '.', $laboratory->latitude) . ")";
}

private function getEPOSlabType(Laboratory $laboratory) {
$fastLaboratoryDomain = $laboratory->fast_domain_name;

switch ($fastLaboratoryDomain) {
case 'Analogue modelling of geological processes':
return '<category:AnalogueModelling_conc>';

case 'Geochemistry':
return '<category:Geochemistry_conc>';

case 'Microscopy and tomography':
return '<category:MicroscopyAndTomography_conc>';

case 'Paleomagnetism':
return '<category:Paleomagnetism_conc>';

case 'Rock and melt physics':
return '<category:RockPhysics_conc>';

default:
return '';
}
}

private function getEquipmentType(LaboratoryEquipment $laboratoryEquipment) {
$equipmentType = $laboratoryEquipment->type_name;

switch ($equipmentType) {
case 'Atomic Force Microscopy':
return '<epos:Atomic_Force_Microscopy_definition>';

case 'Electron Microscopy':
return '<epos:Electron_Microscopy_definition>';

case 'Electron Probe Micro Analyzer':
return '<epos:Electron_Probe_Micro_Analyzer_definition>';

case 'Mass Spectrometer':
return '<epos:Mass_Spectrometer_definition>';

case 'X-Ray Tomography':
return '<epos:X-Ray_Tomography_definition>';

default:
return '';
}
}

private function formatNewlines($text) {
return preg_replace('/\R/', '', nl2br($text, false));
return str_replace(PHP_EOL, '', nl2br($text, false));
}
}

47 changes: 47 additions & 0 deletions app/Fast/Fast.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
namespace App\fast;

use Illuminate\Support\Facades\App;

class Fast
{

protected $client;

public function __construct()
{
if (App::environment('local')) {
$this->client = new \GuzzleHttp\Client(['verify' => false, 'http_errors' => false]);
} else {
$this->client = new \GuzzleHttp\Client(['http_errors' => false]);
}
}


public function facilityRequest($facilityId)
{
$result = new \stdClass();

try {
$response = $this->client->request('GET', "https://fast.geo.uu.nl/api/facility/$facilityId", [
'headers' => [
'Authorization' => config('fast.fast_api_token'),
],
]);
} catch (\Exception $e) {
dd($e->getMessage());
}

$result->response_code = $response->getStatusCode();
$result->response_body = [];

if($result->response_code == 200) {
$result->response_body = json_decode($response->getBody(), true);
}

return $result;
}


}

69 changes: 69 additions & 0 deletions app/Http/Controllers/LabController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\LaboratoryUpdateGroupFast;
use App\Jobs\ProcessLaboratoryUpdateGroupFast;
use App\Exports\epos\RegistryExport;
use App\Models\Laboratory;
use App\Models\LaboratoryOrganization;
use App\Models\LaboratoryOrganizationUpdateGroupRor;
use App\Jobs\ProcessLaboratoryOrganizationUpdateGroupRor;
use App\Jobs\ProcessLaboratoryKeywordUpdateGroup;

class LabController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
}

public function importLabData()
{
return view('import-labdata');
}

public function updateFastData(Request $request)
{
$laboratoryUpdateGroup = LaboratoryUpdateGroupFast::create();
ProcessLaboratoryUpdateGroupFast::dispatch($laboratoryUpdateGroup);

$request->session()->flash('status', 'Updating using Fast started');
return redirect()->route('importers');
}

public function updateLaboratoryOrganizationsByROR(Request $request)
{
$laboratoryOrganizationUpdateGroupRor = LaboratoryOrganizationUpdateGroupRor::create();
ProcessLaboratoryOrganizationUpdateGroupRor::dispatch($laboratoryOrganizationUpdateGroupRor);

$request->session()->flash('status', 'Updating organizations using ROR api');
return redirect()->route('importers');
}

public function updateLaboratoryKeywords(Request $request)
{
ProcessLaboratoryKeywordUpdateGroup::dispatch();

$request->session()->flash('status', 'Updating laboratory keywords using vocabularies');
return redirect()->route('importers');
}

public function registryTurtle(Request $request)
{
$organizations = LaboratoryOrganization::where('ror_country_code', '<>', '')->get();
$exporter = new RegistryExport($organizations);

return response()->streamDownload(function () use($exporter, $request) {
echo $exporter->export();
}, 'msl-labs.ttl');
}


}
Loading

0 comments on commit 5ecc32e

Please sign in to comment.