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

Aus 3908 Custom KML Layers #250

Merged
merged 3 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,193 changes: 2,053 additions & 1,140 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ import { AppRoutingModule } from './app-routing.module';
import { UserStateService } from './services/user/user-state.service';
import { AuthGuard } from './services/auth/auth.guard';
import { AuthService } from './services/auth/auth.service';
import { LayerGroupComponent } from './menupanel/custompanel/layergroup.component';

PlotlyModule.plotlyjs = PlotlyJS;

Expand All @@ -126,6 +127,7 @@ PlotlyModule.plotlyjs = PlotlyJS;
CesiumMapPreviewComponent,
LayerPanelComponent,
CustomPanelComponent,
LayerGroupComponent,
ActiveLayersPanelComponent,
FilterPanelComponent,
SearchPanelComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<p><b>Title: </b>{{cswRecord.name}}</p>
<p><b>Abstract: </b>{{cswRecord.description}}</p>
<p><b>Contact org: </b>{{cswRecord.contactOrg}}</p>
<p *ngIf="cswRecord.funderOrg !== 'Unknown'"><b>Funder: </b>{{cswRecord.funderOrg}}</p>
<p *ngIf="cswRecord.funderOrg && cswRecord.funderOrg !== 'Unknown'"><b>Funder: </b>{{cswRecord.funderOrg}}</p>
<p *ngIf='cleanConstraints(cswRecord.constraints).length > 0'><b>Constraints: </b>{{cleanConstraints(cswRecord.constraints)}}</p>
<p *ngIf='selectConstraints(layer.capabilityRecords, cswRecord.accessConstraints).length > 0'><b>Access Constraints: </b>{{selectConstraints(layer.capabilityRecords, cswRecord.accessConstraints)}}</p>
<p><b>Info URL: </b><a target="_blank" href="{{cswRecord.recordInfoUrl}}">Link to Geonetwork Record</a></p>
<p *ngIf="cswRecord.recordInfoUrl && cswRecord.recordInfoUrl.length > 0"><b>Info URL: </b><a target="_blank" href="{{cswRecord.recordInfoUrl}}">Link to Geonetwork Record</a></p>
<div *ngFor="let onlineResource of cswRecord.onlineResources">
<p *ngIf="onlineResource.type==='DOI'">
<span class="label label-default"><b>DOI Name: </b></span> {{onlineResource.name}}<br>
Expand All @@ -14,8 +14,8 @@
<p *ngIf="onlineResource.type!=='Unsupported' && onlineResource.type!=='DOI'">
<span *ngIf="isGetCapabilitiesType(onlineResource)" class="label label-default"><b>{{onlineResource.type}}: </b></span>
<a *ngIf="isGetCapabilitiesType(onlineResource)" target="_blank" href="{{ onlineResourceGetCapabilitiesUrl(onlineResource) }}">{{onlineResource.type}} GetCapabilities Info</a>
<span *ngIf="!isGetCapabilitiesType(onlineResource)" class="label label-default"><b>{{onlineResource.name.split('_').join(' ')}}: </b></span>
<a *ngIf="!isGetCapabilitiesType(onlineResource)" target="_blank" href="{{ onlineResource.url }}">{{ onlineResource.url }}</a>
<span *ngIf="!isGetCapabilitiesType(onlineResource) && onlineResource.url" class="label label-default"><b>{{onlineResource.name.split('_').join(' ')}}: </b></span>
<a *ngIf="!isGetCapabilitiesType(onlineResource)" target="_blank" href="{{ removeProxy(onlineResource.url) }}">{{ removeProxy(onlineResource.url) }}</a>
</p>
</div>
<p *ngIf="wmsUrl!==undefined">
Expand Down
12 changes: 12 additions & 0 deletions src/app/menupanel/common/infopanel/subpanel/subpanel.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ export class InfoPanelSubComponent implements OnChanges {
return onlineResource.type === 'WMS' || onlineResource.type === 'WFS' || onlineResource.type === 'WCS' || onlineResource.type === 'CSW';
}

/** Removes proxy from URL for display purposes
*
* e.g. "http://localhost:8080/getViaProxy.do?url=https://raw.githubusercontent.com/CesiumGS/cesium/main/Apps/SampleData/kml/bikeRide.kml"
* get converted to "https://raw.githubusercontent.com/CesiumGS/cesium/main/Apps/SampleData/kml/bikeRide.kml"
*
* @param url URL which may be prepended with a reference to the proxy
* @returns URL without the proxy part
*/
public removeProxy(url: string): string {
return url.replace(/^.+getViaProxy\.do\?url=/, "");
}

/**
* Create a WMS/WFS/WCS/CSW GetCapabilities URL from the provided OnlineResource
*
Expand Down
80 changes: 19 additions & 61 deletions src/app/menupanel/custompanel/custompanel.component.html
Original file line number Diff line number Diff line change
@@ -1,74 +1,32 @@
<!-- Custom Layer Input -->
<!-- Custom Layer Input for WMS service -->
<div class="form-group">
<label class="control-label" for="inputSuccess1"><small class="white">WMS Service URL</small></label>
<label class="control-label" for="inputSuccess1"><small class="white">KML URL or OGC WMS Service URL</small></label>
<div class="input-group">
<input type="text" class="form-control" [(ngModel)] = "searchUrl" >
<div class="input-group-append" title="Add WMS service URL layer to map" (click)="search()">
<div class="input-group-append" title="Add KML or OGC WMS service URL layer to map" (click)="search()">
<button class="btn btn-light" type="button" [disabled]="!searchUrl || searchUrl.trim() === ''">
<i *ngIf="!loading" class="fa fa-search"></i>
<i *ngIf="loading" class="fa fa-spinner fa-spin fa-fw"></i>
</button>
</div>
</div>
<span class="help-block small" [innerHTML]="statusmsg">
<span class="help-block small" [innerHTML]="statusMsg">
</span>
</div>
<!-- Once a URL has been selected and processed, its available layers are shown here -->
<layer-group layerGroup [layerGroups]="urlLayerGroups"></layer-group>


<!-- Custom Layer Controls -->
<li class="active" *ngFor="let layerGroup of layerGroups | getKey">
<a href="javascript:;">
<i class="ti-angle-down float-right"></i>
{{layerGroup.key}}
</a>
<ul>
<li *ngFor="let layer of layerGroup.value" [ngClass]="{'active': layer.expanded}">
<a (click)="layer.expanded = !layer.expanded">
<div>
<!--
<span *ngIf="getUILayerModel(layer.id).statusMap.getRenderStarted()" class="float-right project-percentage hasEvent light-blue">
<u (click)="openStatusReport(getUILayerModel(layer.id)); $event.stopPropagation();">{{getUILayerModel(layer.id).statusMap.getCompletePercentage()}}
<i *ngIf="getUILayerModel(layer.id).statusMap.getContainsError()" class="fa fa-warning text-warning"></i>
</u>
</span>
-->
<div>
<i *ngIf="getUILayerModel(layer.id).statusMap.getRenderStarted()" class="fa fa-times red" (click) = "removeLayer(layer);$event.stopPropagation();"></i>
{{layer.name}}
<i *ngIf="layer.expanded && !getUILayerModel(layer.id).statusMap.getRenderStarted()" class="fa fa-arrow-circle-down"></i>
<i *ngIf="getUILayerModel(layer.id).statusMap.getRenderStarted() && !getUILayerModel(layer.id).statusMap.getRenderComplete()" class="float-right light-blue fa fa-spin fa-spinner"></i>
<i *ngIf="getUILayerModel(layer.id).statusMap.getRenderStarted() && getUILayerModel(layer.id).statusMap.getContainsError()" class="fa fa-warning text-warning"></i>
</div>
<!--
<div *ngIf="getUILayerModel(layer.id).statusMap.getRenderStarted()" class="progress progress-xs" style="height: 5px">
<div class="progress-bar bg-gradient-blue-purple" [style.width]="getUILayerModel(layer.id).statusMap.getCompletePercentage()" role="progressbar"></div>
</div>
-->
</div>
</a>
<div [hidden]="!layer.expanded" class="sidebar-card-menu-show">
<div class="card card-info card-with-tabs layer-card animated slideInRight">
<div class = "rh_info_wrap">
<div class="card-header">
<ul id="card-tab" class="nav nav-tabs float-right">
<li (click)="selectTabPanel(layer.id,'filterpanel')" [ngClass]="{'active': getUILayerModel(layer.id).tabpanel.filterpanel.expanded}"><a data-toggle="tab"><span class="d-none d-sm-inline">Map Layer</span></a></li>
</ul>
</div>
<div class="rh_info">
<button (click)="displayRecordInformation(layer)" class="btn-info" title="Information" [ngClass]="{'active': getUILayerModel(layer.id) && getUILayerModel(layer.id).tabpanel.filterpanel.expanded}">info</button>
</div>
</div>
<div id="card-tab-content" class="tab-content">
<div class="tab-pane fade" [ngClass]="{'show active': getUILayerModel(layer.id).tabpanel.filterpanel.expanded}">
<app-filter-panel *ngIf="getUILayerModel(layer.id).tabpanel.filterpanel.expanded" [layer]=layer></app-filter-panel>
</div>
<div class="tab-pane fade" [ngClass]="{'show active': getUILayerModel(layer.id).tabpanel.infopanel.expanded}">
<info-panel *ngIf="getUILayerModel(layer.id).tabpanel.infopanel.expanded" [expanded]="getUILayerModel(layer.id).tabpanel.infopanel.expanded" [cswRecords]=layer.cswRecords [layer]=layer ></info-panel>
</div>
</div>
</div>
</div>
</li>
</ul>
</li>
<hr [hidden]="!layerGroups">
<!-- Custom Layer KML File Input -->
<div class="form-group">
<label class="control-label" for="inputSuccess2"><small class="white">Upload KML file</small></label>
<div class="input-group">
<input type="file" accept=".kml" style="display:none" class="file-input" (change)="onKmlFileSelected($event)" #CustomKmlFileLoader>
<button (click)="CustomKmlFileLoader.click()" type="button" title="Load KML file" class="btn-info btn-sm">
<i class="fa fa-lg fa-file" aria-hidden="true"></i>
&nbsp;Load KML
</button>
</div>
</div>
<!-- Once a file has been uploaded, its layer is shown here -->
<layer-group [layerGroups]="fileLayerGroups"></layer-group>
204 changes: 126 additions & 78 deletions src/app/menupanel/custompanel/custompanel.component.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Component, Output, EventEmitter } from '@angular/core';
import { CsMapService, LayerHandlerService, LayerModel, RenderStatusService } from '@auscope/portal-core-ui';
import { Component, Output, Inject, EventEmitter } from '@angular/core';
import { LayerHandlerService, LayerModel, RenderStatusService, KMLDocService } from '@auscope/portal-core-ui';
import { NgbdModalStatusReportComponent } from '../../toppanel/renderstatus/renderstatus.component';
import { BsModalService } from 'ngx-bootstrap/modal';
import { BsModalRef } from 'ngx-bootstrap/modal';
import { UILayerModel } from '../common/model/ui/uilayer.model';
import { UILayerModelService } from 'app/services/ui/uilayer-model.service';
import { InfoPanelComponent } from '../common/infopanel/infopanel.component';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { LegendUiService } from 'app/services/legend/legend-ui.service';

type LayerGroups = { 'Results': LayerModel[] };

@Component({
selector: '[appCustomPanel]',
Expand All @@ -19,96 +18,145 @@ import { LegendUiService } from 'app/services/legend/legend-ui.service';

export class CustomPanelComponent {

searchUrl: string;
loading: boolean;
statusmsg: string;
// URL that the user types in
searchUrl: string;

layerGroups: {};
bsModalRef: BsModalRef;
@Output() expanded: EventEmitter<any> = new EventEmitter();
// UI loading spinner
loading: boolean;

constructor(private layerHandlerService: LayerHandlerService, private renderStatusService: RenderStatusService,
private modalService: BsModalService, private csMapService: CsMapService, private uiLayerModelService: UILayerModelService,
private legendUiService: LegendUiService, public activeModalService: NgbModal) {
// Used to display info and error messages
statusMsg: string;

// Displays custom layers for URLs in sidebar
urlLayerGroups: LayerGroups = { 'Results': [] };

// Displays custom layers for KML file in sidebar
fileLayerGroups: LayerGroups = { 'Results': [] };

bsModalRef: BsModalRef;
@Output() expanded: EventEmitter<any> = new EventEmitter();

constructor(private layerHandlerService: LayerHandlerService, private renderStatusService: RenderStatusService,
private modalService: BsModalService, private uiLayerModelService: UILayerModelService,
public activeModalService: NgbModal, private kmlService: KMLDocService,
@Inject('env') private env) {
this.loading = false;
this.statusMsg = 'Enter your OGC WMS service endpoint</br>e.g. "https://server.gov.au/service/wms"</br>or KML URL and hit <i class="fa fa-search"></i>.';
}

public selectTabPanel(layerId: string, panelType: string) {
this.uiLayerModelService.getUILayerModel(layerId).tabpanel.setPanelOpen(panelType);
}

/**
* Search list of available WMS layers given an OGC WMS URL or a KML URL
*/
public search() {
// Clear the status message
this.statusMsg = '';

// Clear the results from the previous search, start the loading spinner
this.urlLayerGroups = { 'Results': [] };
this.loading = true;

// Check for empty URL
if (this.searchUrl == undefined) {
this.loading = false;
this.statusmsg = 'Enter your WMS service endpoint URL and hit <i class="fa fa-search"></i>';
this.statusMsg = '<div class="text-danger">Please input the URL you want to search!</div>';
return;
}

public selectTabPanel(layerId: string, panelType: string) {
this.uiLayerModelService.getUILayerModel(layerId).tabpanel.setPanelOpen(panelType);
}
// Trim all whitespace, line terminators, quotes, back quotes and double quotes from ends of URL
const leftTrimRe = /^[\s"'`]+/gms;
const rightTrimRe = /[\s"'`]+$/gms;
const searchUrl = this.searchUrl.replace(leftTrimRe, '').replace(rightTrimRe, '');

/**
* Search list of wms layer given the wms url
*/
public search() {
this.statusmsg = '';
this.loading = true;
if (this.searchUrl == undefined) {
this.loading = false;
this.statusmsg = '<div class="text-danger">Please input the URL you want to search!.</div>';
return
// If KML URL ...
if (searchUrl.toLowerCase().endsWith('.kml')) {
// Create up a special map layer for the KML document
let url;
try {
url = new URL(searchUrl);
} catch (error) {
this.statusMsg = '<div class="text-danger">URL could not be parsed:' + error + '</div>';
return;
}
this.layerHandlerService.getCustomLayerRecord(this.searchUrl).subscribe(response => {
// Extract a layer name from URL
const layerName = url.pathname.split('/').pop();
// Use the proxy
const proxyUrl = this.env.portalBaseUrl + "getViaProxy.do?url=" + searchUrl;
// Make a layer model object
const layerRec: LayerModel = this.layerHandlerService.makeCustomKMLLayerRecord(layerName, proxyUrl, null);
// Configure layers so it can be added to map
const uiLayerModel = new UILayerModel(layerRec.id, this.renderStatusService.getStatusBSubject(layerRec));
this.uiLayerModelService.setUILayerModel(layerRec.id, uiLayerModel);
// Make the layer group listing visible in the UI
this.urlLayerGroups['Results'].unshift(layerRec);
this.loading = false;

} else {
// If OGC WMS Service ...
// Send an OGC WMS 'GetCapabilities' request
this.layerHandlerService.getCustomLayerRecord(searchUrl).subscribe(layerRecs => {
this.loading = false;
if (response != null) {
this.layerGroups = response;
if (Object.keys(this.layerGroups).length === 0) {
this.statusmsg = '<div class="text-danger">No valid layers could be found for this endpoint.</div>';
if (layerRecs != null) {
if (layerRecs.length === 0) {
this.statusMsg = '<div class="text-danger">No valid layers could be found for this endpoint.</div>';
} else {
for (const group in this.layerGroups) {
for (let layer_idx = 0; layer_idx < this.layerGroups[group].length; layer_idx++) {
const uiLayerModel = new UILayerModel(this.layerGroups[group][layer_idx].id, this.renderStatusService.getStatusBSubject(this.layerGroups[group][layer_idx]));
this.uiLayerModelService.setUILayerModel(this.layerGroups[group][layer_idx].id, uiLayerModel);
}
// Evaluate the layers and if found set up loadable map layers
for (const layerRec of layerRecs) {
// Make the layer group listing visible in the UI
this.urlLayerGroups['Results'].unshift(layerRec);
// Configure layers so they can be added to map
const uiLayerModel = new UILayerModel(layerRec.id, this.renderStatusService.getStatusBSubject(layerRec));
this.uiLayerModelService.setUILayerModel(layerRec.id, uiLayerModel);
}
}
} else {
this.statusmsg = '<div class="text-danger">No viable WMS found on the service endpoint. Kindly check your URL again.</div>';
this.statusMsg = '<div class="text-danger">No viable OGC WMS found on the service endpoint. Kindly check your URL again.</div>';
}
});
}
}

/**
* open the modal that display the status of the render
*/
public openStatusReport(uiLayerModel: UILayerModel) {
this.bsModalRef = this.modalService.show(NgbdModalStatusReportComponent, {class: 'modal-lg'});
uiLayerModel.statusMap.getStatusBSubject().subscribe((value) => {
this.bsModalRef.content.resourceMap = value.resourceMap;
});
}

/**
* remove a layer from the map
*/
public removeLayer(layer: LayerModel) {
this.csMapService.removeLayer(layer);
this.legendUiService.removeLegend(layer.id);
}
/**
* Open the modal that displays the status of the render
*
* @param uiLayerModel ui layer model object whose status will be displayed
*/
public openStatusReport(uiLayerModel: UILayerModel) {
this.bsModalRef = this.modalService.show(NgbdModalStatusReportComponent, {class: 'modal-lg'});
uiLayerModel.statusMap.getStatusBSubject().subscribe((value) => {
this.bsModalRef.content.resourceMap = value.resourceMap;
});
}

/**
* Retrieve UILayerModel from the UILayerModelService
* @param layerId ID of layer
*/
public getUILayerModel(layerId: string): UILayerModel {
return this.uiLayerModelService.getUILayerModel(layerId);
/**
* This gets called after a file has been selected for upload
*
* @param event Javascript file selection event object
*/
public onKmlFileSelected(event) {
const me = this;
const file: File = event.target.files[0];
if (file) {
const reader = new FileReader();
// When file has been read this function is called
reader.onload = () => {
let kmlStr = reader.result.toString();
// Remove unwanted characters and inject proxy for embedded URLs
kmlStr = this.kmlService.cleanKML(kmlStr);
const parser = new DOMParser();
const kmlDoc = parser.parseFromString(kmlStr, "text/xml");
// Create up a special map layer for the KML document
const layerRec: LayerModel = this.layerHandlerService.makeCustomKMLLayerRecord(file.name, "", kmlDoc);
const uiLayerModel = new UILayerModel(layerRec.id, this.renderStatusService.getStatusBSubject(layerRec));
this.uiLayerModelService.setUILayerModel(layerRec.id, uiLayerModel);
// Make the layer group listing visible in the UI
me.fileLayerGroups['Results'].unshift(layerRec);
};
// Initiate reading the KML file
reader.readAsText(file);
}

/**
* Display the record information dialog
*
* @param cswRecord CSW record for information
*/
public displayRecordInformation(layer: any) {
if (layer) {
const modelRef = this.activeModalService.open(InfoPanelComponent, {
size: "lg",
backdrop: false
});
modelRef.componentInstance.cswRecords = layer.cswRecords;
modelRef.componentInstance.layer = layer;
}
}

}
}
Loading