diff --git a/projects/portal-core-ui/src/lib/portal-core.module.ts b/projects/portal-core-ui/src/lib/portal-core.module.ts index 8c4a948..f9756b3 100644 --- a/projects/portal-core-ui/src/lib/portal-core.module.ts +++ b/projects/portal-core-ui/src/lib/portal-core.module.ts @@ -16,6 +16,7 @@ import { CsWMSService } from './service/wms/cs-wms.service'; import { CsWFSService } from './service/wfs/cs-wfs.service'; import { CsIrisService } from './service/kml/cs-iris.service'; import { CsKMLService } from './service/kml/cs-kml.service'; +import { CsVMFService } from './service/vmf/cs-vmf.service'; import { KMLDocService } from './service/kml/kml.service'; import { DownloadIrisService } from './service/kml/download-iris.service'; import { GMLParserService } from './utility/gmlparser.service'; @@ -50,6 +51,7 @@ import { PolygonsEditorService } from '@auscope/angular-cesium'; CsWMSService, CsIrisService, CsKMLService, + CsVMFService, KMLDocService, CsMapObject, CsWFSService, diff --git a/projects/portal-core-ui/src/lib/service/cesium-map/cs-map.service.ts b/projects/portal-core-ui/src/lib/service/cesium-map/cs-map.service.ts index 59065f3..30a7cc1 100644 --- a/projects/portal-core-ui/src/lib/service/cesium-map/cs-map.service.ts +++ b/projects/portal-core-ui/src/lib/service/cesium-map/cs-map.service.ts @@ -12,6 +12,7 @@ import { CsWMSService } from '../wms/cs-wms.service'; import { ResourceType } from '../../utility/constants.service'; import { CsIrisService } from '../kml/cs-iris.service'; import { CsKMLService } from '../kml/cs-kml.service'; +import { CsVMFService } from '../vmf/cs-vmf.service'; import { MapsManagerService, RectangleEditorObservable, EventRegistrationInput, CesiumEvent, EventResult } from '@auscope/angular-cesium'; import { Entity, ProviderViewModel, buildModuleUrl, OpenStreetMapImageryProvider, BingMapsStyle, BingMapsImageryProvider, ArcGisMapServerImageryProvider, TileMapServiceImageryProvider, Cartesian2, WebMercatorProjection, SplitDirection } from 'cesium'; @@ -40,6 +41,7 @@ export class CsMapService { private csMapObject: CsMapObject, private manageStateService: ManageStateService, private csCSWService: CsCSWService, private csIrisService: CsIrisService, private csKMLService: CsKMLService, private mapsManagerService: MapsManagerService, + private csVMFService: CsVMFService, @Inject('env') private env, @Inject('conf') private conf) { this.csMapObject.registerClickHandler(this.mapClickHandler.bind(this)); this.addLayerSubject = new Subject(); @@ -194,7 +196,7 @@ export class CsMapService { * @returns a list of supported OnlineResource types as strings */ public getSupportedOnlineResourceTypes(): ResourceType[] { - return [ResourceType.WMS, ResourceType.IRIS, ResourceType.KML, ResourceType.KMZ]; + return [ResourceType.WMS, ResourceType.IRIS, ResourceType.KML, ResourceType.KMZ, ResourceType.VMF]; } /** @@ -240,6 +242,10 @@ export class CsMapService { // Add an IRIS layer this.csIrisService.addLayer(layer, param); this.cacheLayerModelList(layer); + } else if (UtilitiesService.layerContainsResourceType(layer, ResourceType.VMF)) { + // Add a WMS layer to map + this.csVMFService.addLayer(layer, param); + this.cacheLayerModelList(layer); } else if (UtilitiesService.layerContainsResourceType(layer, ResourceType.KMZ)) { // Add a WMS layer to map this.csKMLService.addLayer(layer, param); @@ -318,6 +324,8 @@ export class CsMapService { this.csCSWService.rmLayer(layer); } else if (UtilitiesService.layerContainsResourceType(layer, ResourceType.IRIS)) { this.csIrisService.rmLayer(layer); + } else if (UtilitiesService.layerContainsResourceType(layer, ResourceType.VMF)) { + this.csVMFService.rmLayer(layer); } else if (UtilitiesService.layerContainsResourceType(layer, ResourceType.KML)) { this.csKMLService.rmLayer(layer); } else if (UtilitiesService.layerContainsResourceType(layer, ResourceType.KMZ)) { diff --git a/projects/portal-core-ui/src/lib/service/vmf/cs-vmf.service.ts b/projects/portal-core-ui/src/lib/service/vmf/cs-vmf.service.ts new file mode 100644 index 0000000..d9c6aad --- /dev/null +++ b/projects/portal-core-ui/src/lib/service/vmf/cs-vmf.service.ts @@ -0,0 +1,252 @@ + +import { throwError as observableThrowError, Observable } from 'rxjs'; +import { Inject, Injectable } from '@angular/core'; +import { HttpClient, HttpParams, HttpHeaders, HttpResponse } from '@angular/common/http'; +import { catchError, map } from 'rxjs/operators'; + +import { OnlineResourceModel } from '../../model/data/onlineresource.model'; +import { LayerModel } from '../../model/data/layer.model'; +import { LayerHandlerService } from '../cswrecords/layer-handler.service'; +import { MapsManagerService } from '@auscope/angular-cesium'; +import { ResourceType } from '../../utility/constants.service'; +import { RenderStatusService } from '../cesium-map/renderstatus/render-status.service'; +import { VMFDocService } from './vmf.service'; +import { UtilitiesService } from '../../utility/utilities.service'; + +// NB: Cannot use "import { XXX, YYY, ZZZ, Color } from 'cesium';" - it prevents initialising ContextLimits.js properly +// which causes a 'DeveloperError' when trying to draw the VMF +declare var Cesium; + +/** + * Use Cesium to add layer to map. This service class adds VMF layer to the map + */ +@Injectable() +export class CsVMFService { + + // List of VMF layers that have been cancelled + private cancelledLayers: Array = []; + // Number of VMF resources added for a given layer + private numberOfResourcesAdded: Map = new Map(); + + constructor(private layerHandlerService: LayerHandlerService, + private http: HttpClient, + private renderStatusService: RenderStatusService, + private mapsManagerService: MapsManagerService, + private vmfService: VMFDocService, + @Inject('env') private env) { + } + + /** + * Downloads geojson that is cropped to polygon + * + * @param vmfResource VMF resource to be fetched + * @returns VMF geojson text + */ + private getVMFFeature(url: string, polygon: string): Observable { + /* + const body = + { + "maps": "territories", + "polygon_geojson": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [ + [[111.64, -17.61], [132.45, -8.23], [146.26, -10.53], [159.37, -24.57], [147.95, -45.25], [110.29, -34.29]] + ] + } + } + ] + } + }; + */ + const body= + { + "maps": "territories", + "polygon_geojson": { + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": {}, + "geometry": { + "type": "Polygon", + "coordinates": [JSON.parse(polygon)] + } + } + ] + } + }; + return this.http.post(url, body, { + headers: new HttpHeaders().set('Content-Type', 'application/json'), + responseType: 'text' + }).pipe(map(response => { + return response; + }), catchError((error: HttpResponse) => { + return observableThrowError(error); + }),); + + } + + /** + * Add the VMF layer + * @param layer the VMF layer to add to the map + * @param param parameters for the VMF layer + */ + public addLayer(layer: LayerModel, param?: any): void { + // Remove from cancelled layer list (if present) + this.cancelledLayers = this.cancelledLayers.filter(l => l !== layer.id); + + let vmfOnlineResources: OnlineResourceModel[]; + + if (UtilitiesService.layerContainsResourceType(layer, ResourceType.VMF)) { + vmfOnlineResources = this.layerHandlerService.getOnlineResources(layer, ResourceType.VMF); + } + const me = this; + + // Get CesiumJS viewer + const viewer = this.getViewer(); + const options = { + camera: viewer.scene.camera, + canvas: viewer.scene.canvas, + }; + + for (const onlineResource of vmfOnlineResources) { + // Tell UI that we're about to add a resource to map + this.renderStatusService.addResource(layer, onlineResource); + + // Create data source + const source = new Cesium.GeoJsonDataSource(options); + // Add an event to tell us when loading is finished + source.loadingEvent.addEventListener((evt, isLoading: boolean) => { + if (!isLoading) { + // Tell UI that we have completed updating the map + me.renderStatusService.updateComplete(layer, onlineResource); + } + }); + + if (!this.numberOfResourcesAdded.get(layer.id)) { + this.numberOfResourcesAdded.set(layer.id, 0); + } + + if (UtilitiesService.layerContainsResourceType(layer, ResourceType.VMF)) { + // add VMF to map + + //const proxyUrl = this.env.portalBaseUrl + "getViaProxy.do?usewhitelist=false&url=" + onlineResource.url; + //const proxyUrl = "https://portal.auscope.org.au/" + "getViaProxy.do?usewhitelist=false&url=" + onlineResource.url; + const proxyUrl = onlineResource.url; + const polygon = layer["geojson"]["polygon"]; + var polygonStr = "["; + var delim = ","; + var i = 0; + for (const coord of polygon) { + const lon = coord[0]; + const lat = coord[1]; + if (i == (polygon.length-1)) { delim = ""; } + polygonStr = polygonStr + "["+lon+","+lat+"]"+delim; + i = i + 1; + } + polygonStr = polygonStr + "]"; + this.getVMFFeature(proxyUrl,polygonStr).subscribe(geojsonTxt => { + // make a geojson features collection + let fcTxt = '{"type": "FeatureCollection","features":' + geojsonTxt + '}'; + let geojson = JSON.parse(fcTxt); + + source.load(geojson).then(dataSource => { + if (this.cancelledLayers.indexOf(layer.id) === -1) { + viewer.dataSources.add(dataSource).then(dataSrc => { + + //Get the array of entities + const entities = dataSource.entities.values; + + for (let i = 0; i < entities.length; i++) { + //For each entity (polygon), get the colour property and make the polygon that colour + const entity = entities[i]; + const properties = entity.properties; + const color = properties.color; + let value = color._value; + // remake the color as a hex string (RGB) with opactiy first + value = "0x40" + value.substring(5,7)+value.substring(3,5)+value.substring(1,3); + var cesiumColor = Cesium.Color.fromRgba(value); + //Set the polygon material to our color. + entity.polygon.material = cesiumColor; + //Remove the outlines. + entity.polygon.outline = false; + //Extrude the polygon + entity.polygon.extrudedHeight = 1.0; + } + + layer.csLayers.push(dataSrc); + this.incrementLayersAdded(layer, vmfOnlineResources.length); + }, (err) => { + console.error('Unable to add viewer.dataSources VMF: ', err); + }); + } + }, (err) => { + console.error('Unable to load source.load VMF: ', err); + }); + }, (err) => { + alert('Unable to load VMF: ' + err.message); + console.error('Unable to load VMF: ', err); + // Tell UI that we have completed updating the map & there was an error + this.renderStatusService.updateComplete(layer, onlineResource, true); + this.incrementLayersAdded(layer, vmfOnlineResources.length); + }); + + } + + } + } + + /** + * Increment the number of layers added for a given LayerModel, and clear the layer from the + * cancelled layer list if all layers have been added + * @param layer the LayerModel + * @param totalLayers total number of layers for LayerModel + */ + private incrementLayersAdded(layer: LayerModel, totalLayers: number) { + this.numberOfResourcesAdded.set(layer.id, this.numberOfResourcesAdded.get(layer.id) + 1); + if (this.numberOfResourcesAdded.get(layer.id) === totalLayers) { + this.cancelledLayers = this.cancelledLayers.filter(l => l !== layer.id); + } + } + + /** + * Request cancellation of layer if it's still being added + * @param layerId ID of layer + */ + public cancelLayerAdded(layerId: string) { + if (this.cancelledLayers.indexOf(layerId) === -1) { + this.cancelledLayers.push(layerId); + } + } + + /** + * Removes VMF layer from the map + * @method rmLayer + * @param layer the VMF layer to remove from the map. + */ + public rmLayer(layer: LayerModel): void { + // Request cancellation of layer if it's still being added + this.cancelLayerAdded(layer.id); + + const viewer = this.getViewer(); + for (const dataSrc of layer.csLayers) { + viewer.dataSources.remove(dataSrc); + } + layer.csLayers = []; + this.renderStatusService.resetLayer(layer.id); + } + + /** + * Fetches Cesium 'Viewer' + */ + private getViewer() { + return this.mapsManagerService.getMap().getCesiumViewer(); + } + +} diff --git a/projects/portal-core-ui/src/lib/service/vmf/vmf.service.ts b/projects/portal-core-ui/src/lib/service/vmf/vmf.service.ts new file mode 100644 index 0000000..6085f08 --- /dev/null +++ b/projects/portal-core-ui/src/lib/service/vmf/vmf.service.ts @@ -0,0 +1,44 @@ +import { Injectable, Inject } from '@angular/core'; + +/** + * This service class contains functions used for manipulating VMF documents + */ +@Injectable({ + providedIn: 'root' +}) +export class VMFDocService { + + constructor(@Inject('env') private env) { } + + /** + * Clean VMF text by removing illegal chars and + * forcing proxying of icon images to avoid CORS errors + * + * @param vmfTxt VMF text to be cleaned + * @returns clean VMF string + */ + public cleanVMF(vmfTxt: string): string { + // Removes non-standard chars that can cause errors + vmfTxt = vmfTxt.replace(/\016/g, ''); + vmfTxt = vmfTxt.replace(/\002/g, ''); + // Inserts local paddle image to avoid CORS errors + // Cesium does not load proxied images for some as yet unknown reason + vmfTxt = vmfTxt.replace(/\s*.*<\/href>/g, + '\nextension/images/white-paddle.png'); + return vmfTxt; + } + + /** + * Clean VMF text by removing illegal chars + * future: when cesium support proxying of images + * + * @param vmfTxt VMF text to be cleaned + * @returns clean VMF string + */ + public cleanKMZ(vmfTxt: string): string { + // Removes non-standard chars that can cause errors + vmfTxt = vmfTxt.replace(/\016/g, ''); + vmfTxt = vmfTxt.replace(/\002/g, ''); + return vmfTxt; + } +} diff --git a/projects/portal-core-ui/src/lib/utility/constants.service.ts b/projects/portal-core-ui/src/lib/utility/constants.service.ts index 9d74792..5e5f743 100644 --- a/projects/portal-core-ui/src/lib/utility/constants.service.ts +++ b/projects/portal-core-ui/src/lib/utility/constants.service.ts @@ -14,6 +14,7 @@ export enum ResourceType { OTHERS = 'OTHERS', SOS = "SOS", UNSUPPORTED = 'Unsupported', + VMF = "VMF", WMS = "WMS", WFS = "WFS", WCS = "WCS", diff --git a/projects/portal-core-ui/src/public-api.ts b/projects/portal-core-ui/src/public-api.ts index bfc6d7b..50ad701 100644 --- a/projects/portal-core-ui/src/public-api.ts +++ b/projects/portal-core-ui/src/public-api.ts @@ -34,6 +34,7 @@ export { QueryWMSService } from './lib/service/wms/query-wms.service'; export { CsWWWService } from './lib/service/www/cs-www.service'; export { CsIrisService } from './lib/service/kml/cs-iris.service'; export { CsKMLService } from './lib/service/kml/cs-kml.service'; +export { CsVMFService } from './lib/service/vmf/cs-vmf.service'; export { DownloadIrisService } from './lib/service/kml/download-iris.service'; export { KMLDocService } from './lib/service/kml/kml.service';