Skip to content

Commit

Permalink
Merge pull request #250 from AuScope/AUS-3908
Browse files Browse the repository at this point in the history
Aus 3908  Custom KML Layers
  • Loading branch information
laughing0li authored Apr 11, 2023
2 parents ef58c20 + c992d71 commit d30afb3
Show file tree
Hide file tree
Showing 9 changed files with 10,193 additions and 9,144 deletions.
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

0 comments on commit d30afb3

Please sign in to comment.