Skip to content

Commit

Permalink
AUS-3693 Suppport GeoSciML v4.1 complex boreholes with petrophysics data
Browse files Browse the repository at this point in the history
  • Loading branch information
vjf committed Nov 29, 2022
1 parent 70bb379 commit d09bf7c
Show file tree
Hide file tree
Showing 10 changed files with 195 additions and 77 deletions.
3 changes: 2 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { MSCLComponent } from './modalwindow/querier/customanalytic/mscl/mscl.component';
import { MSCLAnalyticComponent } from './modalwindow/layeranalytic/mscl/mscl.analytic.component';
import { MSCLService } from './modalwindow/layeranalytic/mscl/mscl.service';

import { HelpMenuComponent } from './toppanel/help-menu/help-menu.component';

Expand Down Expand Up @@ -133,7 +134,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
DataExplorerComponent,
RecordModalComponent
],
providers: [ AuscopeApiService, FilterService, RectanglesEditorService, AdvancedComponentService, SearchService, NVCLService, GraceService, {provide: SAVER, useFactory: getSaver} ],
providers: [ AuscopeApiService, FilterService, RectanglesEditorService, AdvancedComponentService, SearchService, NVCLService, MSCLService, GraceService, {provide: SAVER, useFactory: getSaver} ],
imports: [
PortalCoreModule.forRoot(environment, config),
PortalCorePipesModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -720,7 +720,7 @@ export class DownloadPanelComponent implements OnInit {
if (UtilitiesService.isEmpty(err.message)) {
alert('An error has occurred whilst attempting to download. Please contact cg-admin@csiro.au');
} else {
alert('An error has occurred whilst attempting to download (' + err.message + '). Plese contact cg-admin@csiro.au');
alert('An error has occurred whilst attempting to download (' + err.message + '). Please contact cg-admin@csiro.au');
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class MSCLAnalyticComponent implements OnInit {
featureId: string; // Identifier of the borehole
metricList: string[]; // List of metric enums to plot
closeGraphModal: () => null; // Function to call when the modal dialogue must be closed
usesGMLObs: boolean; // Response has values nested within GeoSciML observations
serviceUrl: string; // URL of MSCL service
processingData = false;

Expand Down Expand Up @@ -42,7 +43,7 @@ export class MSCLAnalyticComponent implements OnInit {
const error_display = this.error_display.nativeElement;
// Fetch data from MSCL service
this.processingData = true;
this.msclService.getMSCLDownload(this.serviceUrl, this.featureId, this.startDepth, this.endDepth, this.metricList).subscribe(valuesList => {
this.msclService.getMSCLDownload(this.serviceUrl, this.featureId, this.startDepth, this.endDepth, this.usesGMLObs, this.metricList).subscribe(valuesList => {
// Check response
if (valuesList == null || !(Symbol.iterator in Object(valuesList))) {
this.processingData = false;
Expand All @@ -60,7 +61,11 @@ export class MSCLAnalyticComponent implements OnInit {
for (const values of valuesList) {
for (const metricEnum of this.metricList) {
const featName = this.msclService.getMetricInfoAttr(metricEnum, 'feat_elem');
xLists[metricEnum].push(values[featName]);
if (this.usesGMLObs) {
xLists[metricEnum].push(values[featName.replace(/_/g, ' ')]);
} else {
xLists[metricEnum].push(values[featName]);
}
}
yList.push(values.depth);
}
Expand Down
96 changes: 92 additions & 4 deletions src/app/modalwindow/layeranalytic/mscl/mscl.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpParams, HttpHeaders, HttpResponse } from '@angular/common/http';
import { environment } from '../../../../environments/environment';
import { Layout, Data } from 'plotly.js-dist-min';
import { SimpleXMLService } from '@auscope/portal-core-ui';

// Elements detectable via XRF
const XRFElem = ['Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'As', 'Se', 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo',
Expand All @@ -20,6 +21,7 @@ export enum Metric { diameter = "diameter",
pWaveAmp = "pWaveAmp",
pWaveVel = "pWaveVel",
density = "density",
specificGravity = "specificGravity",
magSuscPoint = "magSuscPoint",
magSuscLoopVC = "magSuscLoopVC",
magSuscLoopDC = "magSuscLoopDC",
Expand Down Expand Up @@ -61,6 +63,7 @@ let metricMap: Map<string, Info> = new Map( [
[ Metric.pWaveVel, { pname: 'P-Wave Vel.', group: 'P-Wave', desc: 'P-Wave Velocity', units: 'm/s', feat_elem: 'p_wave_velocity'}],
[ Metric.pWaveAmp, { pname: 'P-Wave Amp.', group: 'P-Wave', desc: 'P-Wave Amplitude', units: '', feat_elem: 'p_wave_amplitude'}],
[ Metric.density, { pname: 'Density', group: '', desc: 'Density', units: '', feat_elem: 'density'} ],
[ Metric.specificGravity, { pname: 'Specific Gravity', group: '', desc: 'Specfic Gravity', units: '', feat_elem: 'specific_gravity'} ],
[ Metric.magSuscPoint, { pname: 'Mag. Susc. Point', group: '', desc: 'Magnetic Susceptibility Point', units: 'SI x 10^-5', feat_elem: 'magnetic_susc_point'} ],
[ Metric.magSuscLoopVC, { pname: 'Mag. Susc. LoopVC', group: '', desc: 'Magnetic Susceptibility Loop Volume Corrected', units: 'SI x 10^-5', feat_elem: 'magnetic_susceptibility'} ],
[ Metric.magSuscLoopDC, { pname: 'Mag. Susc. LoopDC', group: '', desc: 'Magnetic Susceptibility Loop Density Corrected', units: 'SI x 10^-5', feat_elem: 'magnetic_susc_loop_dc'} ],
Expand Down Expand Up @@ -134,7 +137,7 @@ export class MSCLService {
// Convert feature name list to a list of names and group names
for (let featElem of featList) {
for (let mm of metricMap.values()) {
if (mm.feat_elem === featElem) {
if (mm.feat_elem === featElem.replace(/ /g, '_')) {
if (!retList.includes(mm.pname)) {
retList.push(mm.pname);
}
Expand Down Expand Up @@ -441,6 +444,83 @@ export class MSCLService {
return traceList;
}

/**
* Checks to see if there are petrophysics observation sample values
* Assumes GeoSciML v4.1 WFS response format
*
* @param XML string WFS response
* @returns true if values could be found
*/
public usesGMLObs(xmlStr: string): boolean {
const obsIdx = xmlStr.search(/<om:OM_Observation (gml:)?id="om.observation.petrophysicalproperty/);
return (obsIdx > 0);
}

/**
* Namespace resolver for XPATH parsing of GeoSciML v4.1 WFS response
*/
private nsResolver(prefix: any) {
switch (prefix) {
case 'wfs':
return "http://www.opengis.net/wfs";
case "xs":
return "http://www.w3.org/2001/XMLSchema";
case "swe":
return "http://www.opengis.net/swe/2.0";
case "sams":
return "http://www.opengis.net/samplingSpatial/2.0";
case "gsmlp":
return "http://xmlns.geosciml.org/geosciml-portrayal/4.0";
case "gml":
return "http://www.opengis.net/gml";
case "mt":
return "http://xmlns.geoscience.gov.au/mineraltenementml/1.0";
case "cit":
return "http://standards.iso.org/iso/19115/-3/cit/1.0";
case "gco2":
return "http://www.isotc211.org/2005/gco";
case "gsmlb":
return "http://www.opengis.net/gsml/4.1/GeoSciML-Basic";
case "gsml":
return "urn:cgi:xmlns:CGI:GeoSciML:2.0";
case "gsmlbh":
return "http://www.opengis.net/gsml/4.1/Borehole";
case "erl":
return "http://xmlns.earthresourceml.org/earthresourceml-lite/2.0";
case "om":
return "http://www.opengis.net/om/2.0";
case "sam":
return "http://www.opengis.net/sampling/2.0";
case "xlink":
return "http://www.w3.org/1999/xlink";
case "gmd":
return "http://www.isotc211.org/2005/gmd";
case "xsi":
return "http://www.w3.org/2001/XMLSchema-instance";
}
return "http://www.http://www.w3.org/2001/XMLSchema-instance.net/wcs";
}

/**
* Parses an XML WFS response to find the set of metric types available
* Applies only to GeoSciML v4.1
*
* @param xmlStr WFS response
* @returns a list of strings or empty list if not found
*/
public findMetricTypes(xmlStr: string): any[] {
const rootNode = SimpleXMLService.parseStringToDOM(xmlStr);
const METRICS = '//gsmlbh:specification/om:OM_Observation/om:result/swe:Quantity/swe:label';
const nodeList = SimpleXMLService.evaluateXPathNodeArray(rootNode, rootNode, METRICS, this.nsResolver);
const metricVals = [];
for (const node of nodeList) {
metricVals.push(node.textContent);
}
const metricSet = new Set(metricVals);
const uniqueMetrics = [ ... metricSet ];
return uniqueMetrics;
}


/**
* Contacts the MSCL data service and retrieves plot data
Expand All @@ -449,10 +529,12 @@ export class MSCLService {
* @param boreholeHeaderId borehole identifier
* @param startDepth retrieve plot data starting at this depth
* @param endDepth retrieve plot data ending at this depth
* @param useGMLObs if true then observations are hidden in complete GeoSciML data model
* @param metricList list of metrics for which plotting data is required
* @return Observable for waiting on
*/
public getMSCLDownload(serviceUrl: string, boreholeHeaderId: string, startDepth: number, endDepth: number, metricList: string[]): Observable<any> {
public getMSCLDownload(serviceUrl: string, boreholeHeaderId: string, startDepth: number, endDepth: number,
useGMLObs: boolean, metricList: string[]): Observable<any> {
let httpParams = new HttpParams();
httpParams = httpParams.append('serviceUrl', serviceUrl);
httpParams = httpParams.append('boreholeHeaderId', boreholeHeaderId);
Expand All @@ -462,15 +544,21 @@ export class MSCLService {
for (const metric of metricList) {
const feat_elem = this.getMetricInfoAttr(metric, 'feat_elem');
if (feat_elem != '') {
httpParams = httpParams.append('observationsToReturn', feat_elem);
const std_feat_elem = (useGMLObs) ? feat_elem.replace('_',' '): feat_elem;
httpParams = httpParams.append('observationsToReturn', std_feat_elem);
// If user requested a group name, append all members of group
} else if (this.isMetricGroup(metric)) {
const gMetricList = this.getInfoAttrsForGrp(metric, 'feat_elem');
for (let gMet of gMetricList) {
httpParams = httpParams.append('observationsToReturn', gMet);
const cleanMetric = (useGMLObs) ? gMet.replace('_',' '): gMet;
httpParams = httpParams.append('observationsToReturn', cleanMetric);
}
}
}
// Observations are hidden in complete GeoSciML data model
if (useGMLObs) {
httpParams = httpParams.append('useGMLObs', 'true');
}
// Send HTTP request for observations for a borehole
return this.http.post(environment.portalBaseUrl + 'getMsclObservationsForGraph.do', httpParams.toString(), {
headers: new HttpHeaders().set('Content-Type', 'application/x-www-form-urlencoded'),
Expand Down
20 changes: 16 additions & 4 deletions src/app/modalwindow/querier/customanalytic/mscl/mscl.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export class MSCLComponent implements OnInit {
public allTicked = false; // Are all tickboxes ticked?
public showSelectMetricError: boolean; // Show error if no metrics chosen when Draw Graph is pressed

private usesGMLObs = false; // Response has values nested within GeoSciML observations
private bsModalRef: BsModalRef;

constructor(public msclService: MSCLService, private modalService: BsModalService, private changeDetectorRef: ChangeDetectorRef) {
Expand All @@ -47,9 +48,19 @@ export class MSCLComponent implements OnInit {
// Extract the available metrics from the "datasetProperties" XML element in the WFS response
// "datasetProperties" is a list of the metrics available in this borehole's dataset
// The members of the list take the form of XML element names e.g. p_wave_velocity
const metrics = /<gsmlp:datasetProperties>[a-z_,]*<\/gsmlp:datasetProperties>/.exec(this.doc.raw).toString();
// Remove tags at ends and convert to a list
const metricList = metrics.substring(25, metrics.length - 26).split(",");
let metricList = [];
// Find out if values are nested within GeoSciML observations
this.usesGMLObs = this.msclService.usesGMLObs(this.doc.raw);
if (this.usesGMLObs) {
metricList = this.msclService.findMetricTypes(this.doc.raw);
} else {
const searchResult = /<gsmlp:datasetProperties>[a-z_,]*<\/gsmlp:datasetProperties>/.exec(this.doc.raw);
if (searchResult) {
const metricsStr = searchResult.toString();
// Remove tags at ends and convert to a list
metricList = metricsStr.substring(25, metricsStr.length - 26).split(",");
}
}
this.metricPNameList = this.msclService.getMetricPNameList(metricList)

// Given list of metrics, set up the data structures that support tickboxes
Expand Down Expand Up @@ -115,7 +126,8 @@ export class MSCLComponent implements OnInit {
'metricList': selecMetricList,
'featureId': this.featureId,
'closeGraphModal': this.closeGraphModal.bind(this),
'serviceUrl': this.onlineResource.url
'serviceUrl': this.onlineResource.url,
'usesGMLObs': this.usesGMLObs
}
});
this.modalDisplayed = true;
Expand Down
9 changes: 3 additions & 6 deletions src/app/modalwindow/querier/dynamic.analytic.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { LayerModel } from '@auscope/portal-core-ui';
import { OnlineResourceModel } from '@auscope/portal-core-ui';
import { NVCLDatasetListComponent } from './customanalytic/nvcl/nvcl.datasetlist.component';
import { Component, Input, ViewChild, ComponentFactoryResolver, ViewContainerRef, ChangeDetectorRef } from '@angular/core';
import { Component, Input, ViewChild, ViewContainerRef, ChangeDetectorRef } from '@angular/core';
import {ref} from '../../../environments/ref';
import { QuerierInfoModel } from '@auscope/portal-core-ui';
import { RemanentAnomaliesComponent } from './customanalytic/RemanentAnomalies/remanentanomalies.component';
Expand All @@ -24,7 +24,7 @@ export class DynamicAnalyticComponent {
dyanmicAnalyticHost: ViewContainerRef;


constructor(private componentFactoryResolver: ComponentFactoryResolver, private changeDetectorRef: ChangeDetectorRef ) { }
constructor(private changeDetectorRef: ChangeDetectorRef) { }

@Input()
set load(load: boolean) {
Expand All @@ -36,12 +36,9 @@ export class DynamicAnalyticComponent {

loadComponent() {


const componentFactory = this.componentFactoryResolver.resolveComponentFactory(ref.analytic[this.layer.id]);

const viewContainerRef = this.dyanmicAnalyticHost
viewContainerRef.clear();
const componentRef = viewContainerRef.createComponent(componentFactory);
const componentRef = viewContainerRef.createComponent(ref.analytic[this.layer.id]);

(<NVCLDatasetListComponent>componentRef.instance).layer = this.layer;
(<NVCLDatasetListComponent>componentRef.instance).onlineResource = this.onlineResource;
Expand Down
20 changes: 11 additions & 9 deletions src/app/modalwindow/querier/querier.modal.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ <h4 class="card-title">
<div class="card-header">
<ul id="querier-card-tab" class="nav nav-tabs float-right">
<li (click)="doc.home=true;doc.analytic=false;onDataChange()" [ngClass]="{'active':doc.home}"><a href="#{{doc.key}}-card-tab-home" data-toggle="tab"><i class="fa fa-list"></i><span class="d-none d-md-inline"> WFS</span></a></li>
<li (click)="doc.home=false;doc.analytic=true;onDataChange()" [ngClass]="{'active':doc.analytic}" title="Show analytic" *ngIf="(isNVCL(doc.layer.id) && flagAnalytic || !isNVCL(doc.layer.id)) && analyticMap[doc.layer.id]" ><a href="#{{doc.key}}-card-tab-profile" data-toggle="tab"><i class="fa fa-line-chart"></i><span class="d-none d-md-inline">&nbsp;Analytic</span></a></li>
<li (click)="doc.home=false;doc.analytic=true;onDataChange()" [ngClass]="{'active':doc.analytic}" title="Show analytic" *ngIf="(isNVCL(doc.layer.id) && flagAnalytic || !isNVCL(doc.layer.id)) && analyticMap[doc.layer.id] || hasMsclAnalytics" ><a href="#{{doc.key}}-card-tab-profile" data-toggle="tab"><i class="fa fa-line-chart"></i><span class="d-none d-md-inline">&nbsp;Analytic</span></a></li>
</ul>
<h4 *ngIf="doc.home" class="card-title">Feature Detail</h4>
<h4 *ngIf="doc.analytic" class="card-title">Analytic</h4>
Expand All @@ -64,30 +64,32 @@ <h4 *ngIf="doc.analytic" class="card-title">Analytic</h4>
<!-- Show HTML if XML is transformed -->
<div *ngIf="doc.transformed" [innerHtml]="doc.transformed"></div>
<!-- Show default XML Tree if there is no HTML transformation -->
<mat-tree *ngIf="nestedDataSource[doc.key]" [dataSource]="nestedDataSource[doc.key]" [treeControl]="nestedTreeControl[doc.key]"
<mat-tree *ngIf="flatTreeDataSource[doc.key]" [dataSource]="flatTreeDataSource[doc.key]" [treeControl]="flatTreeControl[doc.key]"
class="example-tree">
<dl>
<mat-tree-node *matTreeNodeDef="let node" style="font-size: 12px !important;padding: 3px; min-height: 0px">
<!-- Node without children -->
<mat-tree-node *matTreeNodeDef="let node" style="font-size: 12px !important; min-height: 0px" [matTreeNodePaddingIndent]="15" matTreeNodePadding>
<dt style="flex: auto;">{{node.filename}}</dt>
<dd>{{node.type}}
<a *ngIf="node.type && node.type.toString().startsWith('http')" target="_blank" href="{{node.type}}">
<i class="fa fa-external-link-square" aria-hidden="true"></i>
</a>
</dd>
</mat-tree-node>
<mat-nested-tree-node *matTreeNodeDef="let node; when: hasNestedChild">
<dt style="flex: auto;">
<button mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.filename">
<!-- Node with children -->
<mat-tree-node *matTreeNodeDef="let node; when: hasChild" [matTreeNodePaddingIndent]="15" matTreeNodePadding>
<dt style="flex: auto; font-size: 12px !important">
<button mat-icon-button matTreeNodeToggle [matTreeNodeToggleRecursive]="true" [attr.aria-label]="'toggle ' + node.filename">
<mat-icon class="mat-icon-rtl-mirror">
{{nestedTreeControl[doc.key].isExpanded(node) ? 'expand_more' : 'chevron_right'}}
{{flatTreeControl[doc.key].isExpanded(node) ? 'expand_more' : 'chevron_right'}}
</mat-icon>
</button>{{node.filename}}</dt>
<dd>
<dl [class.feature-tree-invisible]="!nestedTreeControl[doc.key].isExpanded(node)" style="margin-left:10px">
<dl [class.feature-tree-invisible]="!flatTreeControl[doc.key].isExpanded(node)" style="margin-left:10px">
<ng-container matTreeNodeOutlet></ng-container>
</dl>
</dd>
</mat-nested-tree-node>
</mat-tree-node>
</dl>
</mat-tree>

Expand Down
Loading

0 comments on commit d09bf7c

Please sign in to comment.