Skip to content

Commit

Permalink
[Fix] Footprint, regular member visible elements, validate image WMS (#…
Browse files Browse the repository at this point in the history
…992)

- [Fix] remove a footprint from search params on a polygon removal from the map
- Remove a invitation edition
- [Fix] Hide sub navigation in an institution profile if a user is just a member
- [Fix] Image WMS validation while add a new overlay
  - Fetch capabilitites and set URL LAYERS AND STYLES if are undefined
  - BBOX URL param now can have a negative values
  - Server reponse should validate response content-type and should be image/*
  • Loading branch information
danielkryska authored Dec 4, 2020
1 parent 600608e commit de38b29
Show file tree
Hide file tree
Showing 15 changed files with 102 additions and 352 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,10 @@

<ng-container *ngIf="(isAddLoading$ | async)">
<div>
<p>URL zdjęcia WMS może zawierać następujące parametry:</p>
<ol>
<li>
<b>LAYERS=</b>
lista oddzielonych przecinkami nazw warstw (OPCJONALNE)
</li>
<li>
<b>STYLES=</b>
lista oddzielonych przecinkami typów renderingu dla każdej
z podanych warstw (OPCJONALNE)
</li>
<br>
</ol>

<p>
Parametry akceptowalne i ich wartości,
usuwane podczas przetwarzania URL dla prawidłowego wyświetlania obrazu:
Jeśli podany URL nie posiada parametru <b>LAYERS</b> to zostanie on
uzupełniony nazwami wszystkich warstw zwróconych przez metodę GetCapabilities podanego serwera WMS.
</p>
<ol>
<li><b>VERSION=</b> z wartościami numerycznymi w formacie x.y.z</li>
<li><b>REQUEST=GetMap</b></li>
<li><b>BBOX=</b> z wartością w formacie minx,miny,maxx,maxy</li>
<li><b>SRS=</b> lub <b>CRS=</b> w formacie nazwa przestrzeni:identyfikator</li>
<li><b>WIDTH=</b> w formacie numerycznym</li>
<li><b>HEIGHT=</b> w formacie numerycznym</li>
<li><b>FORMAT=</b> w formacie image/rozszerzenie zdjęcia (vnd.jpeg-png|vnd.jpeg-png8|png|gif|tiff|jpg)</li>
<li><b>SERVICE=WMS</b></li>
<li><b>TRANSPARENT=</b> z wartością false lub true</li>
</ol>

<br>
</div>
<form [formGroup]="newOverlayForm" class="form">
Expand Down
78 changes: 59 additions & 19 deletions s4e-web/src/app/components/overlay-list/overlay-list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ import {OverlayQuery} from '../../views/map-view/state/overlay/overlay.query';
import {disableEnableForm, validateAllFormFields} from '../../utils/miscellaneous/miscellaneous';
import {OverlayService} from '../../views/map-view/state/overlay/overlay.service';
import {FormControl, FormGroup, Validators} from '@ng-stack/forms';
import {removeAutoGeneratedParams, WMS_URL_VALIDATORS} from './wms-url.utils';
import {Observable, Subject} from 'rxjs';
import {WMS_URL_VALIDATORS} from './wms-url.utils';
import {forkJoin, Observable, of, Subject} from 'rxjs';
import Projection from 'ol/proj/Projection';
import {filter, map, switchMap, takeUntil, tap} from 'rxjs/operators';
import {untilDestroyed} from 'ngx-take-until-destroy';
import {ActivatedRoute} from '@angular/router';
import WMSCapabilities from 'ol/format/WMSCapabilities';

export interface OverlayForm {
url: string;
Expand Down Expand Up @@ -131,22 +132,20 @@ export class OverlayListComponent implements OnInit, OnDestroy {

addNewOverlay() {
validateAllFormFields(this.newOverlayForm);

const sourceUrl = this.newOverlayForm.controls.url.value;
const url = removeAutoGeneratedParams(sourceUrl);
if (this.newOverlayForm.invalid) {
return;
}

const url = this.newOverlayForm.controls.url.value;
if (this.newOwner === 'INSTITUTIONAL') {
this.hasLoadingError$(url)
.pipe(
untilDestroyed(this),
switchMap(() => this._activatedRoute.queryParamMap),
map(params => params.get('institution'))
switchMap(formattedUrl => forkJoin([of(formattedUrl), this._activatedRoute.queryParamMap])),
map(([url, params]) => [url, params.get('institution')])
)
.subscribe(
institutionSlug => this._overlayService
([url, institutionSlug]) => this._overlayService
.createInstitutionalOverlay({...this.newOverlayForm.value, url}, institutionSlug),
(error: string) => this._notificationService.addGeneral({content: error, type: 'error'})
);
Expand All @@ -156,13 +155,13 @@ export class OverlayListComponent implements OnInit, OnDestroy {
this.hasLoadingError$(url)
.pipe(untilDestroyed(this))
.subscribe(
() => {
formattedUrl => {
switch (this.newOwner) {
case 'PERSONAL':
this._overlayService.createPersonalOverlay({...this.newOverlayForm.value, url});
this._overlayService.createPersonalOverlay({...this.newOverlayForm.value, url: formattedUrl});
break;
case 'GLOBAL':
this._overlayService.createGlobalOverlay({...this.newOverlayForm.value, url});
this._overlayService.createGlobalOverlay({...this.newOverlayForm.value, url: formattedUrl});
break;
}
},
Expand All @@ -175,15 +174,37 @@ export class OverlayListComponent implements OnInit, OnDestroy {
* Observable is used due to not working Open Layers Event Dispatcher
* and force state changes
*/
url = url.toLowerCase().includes('request=getcapabilities') ? url.split("?")[0] : url;
return new Observable<string | null>(observer$ => {
const source = getImageWmsFrom({url});
source.setImageLoadFunction(getImageWmsLoader(observer$));

const BBOX = [49.497526,14.407200,54.573956,23.836237];
const standardCrs = 'EPSG:3857';
source
.getImage(BBOX, 1, 1, new Projection({code: standardCrs}))
.load();
const capabilitiesUrl = `${url.split('?')[0]}?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetCapabilities`;
fetch(capabilitiesUrl)
.then(response => response.text())
.then(text => (new WMSCapabilities()).read(text))
.then(parsedCapabilities => parsedCapabilities.Capability.Layer)
.then(layerMetadata => {
const {crs, extent, ...rest} = layerMetadata.BoundingBox[0];

if (!url.includes('LAYERS')) {
const layers = this._unpackLayers(layerMetadata)
.filter(layer => !!layer.Name)
.map(layer => layer.Name)
.join(',');

const hasParams = url.includes("?");

url = hasParams ? `${url}&LAYERS=${layers}` : `${url.split('?')[0]}?LAYERS=${layers}`;
}

if (!url.includes('STYLES=')) {
url = `${url}&STYLES=`
}

const source = getImageWmsFrom({url});
source.setImageLoadFunction(getImageWmsLoader(observer$));
source
.getImage(extent, 1000,0.01, new Projection({code: crs}))
.load();
});
});
}

Expand All @@ -203,4 +224,23 @@ export class OverlayListComponent implements OnInit, OnDestroy {
ngOnDestroy(): void {
this._overlayService.resetUI();
}

private _unpackLayers(layer: any, depth = 0) {
const MAX_DEPTH = 10;

if (!layer) {
return [];
}

if (!layer.Layer || depth > MAX_DEPTH) {
return [layer];
}

return [
layer,
...layer.Layer
.map(layer => this._unpackLayers(layer, depth + 1))
.reduce((finalLayers, layers) => finalLayers = [...finalLayers, ...layers])
];
}
}
43 changes: 13 additions & 30 deletions s4e-web/src/app/components/overlay-list/wms-url.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {Overlay} from '../../views/map-view/state/overlay/overlay.model';

const VERSION_EXPRESSION = /version=\d+\.\d+\.\d+(&?)/i;
const REQUEST_EXPRESSION = /request=[a-zA-Z]*(&?)/i;
const LAYERS_EXPRESSION = /layers=[a-zA-Z._0-9,:]*(&?)/i;
const STYLES_EXPRESSION = /styles=[a-zA-Z._0-9,:]*(&?)/i;
const BBOX_EXPRESSION = /bbox=[.0-9]*,[.0-9]*,[.0-9]*,[.0-9]*(&?)/i;
const LAYERS_EXPRESSION = /layers=[a-zA-Z._0-9,:-]*(&?)/i;
const STYLES_EXPRESSION = /styles=[a-zA-Z._0-9,:-]*(&?)/i;
const BBOX_EXPRESSION = /bbox=(-?)[.0-9]*,(-?)[.0-9]*,(-?)[.0-9]*,(-?)[.0-9]*(&?)/i;
const SRS_OR_CRS_EXPRESSION = /(srs|crs)=[a-zA-Z._0-9]*:[a-zA-Z._0-9]*(&?)/i;
const WIDTH_EXPRESSION = /width=[0-9]*(&?)/i;
const HEIGHT_EXPRESSION = /height=[0-9]*(&?)/i;
Expand All @@ -15,10 +15,12 @@ const WMS_SERVICE_EXPRESSION = /service=wms(&?)/i;
const TRANSPARENT_EXPRESSION = /transparent=(false|true)(&?)/i;

/**
* List of params to be removed from url
* due to autogenerated duplicates by openlayers lib
*/
const PARAMS_TO_BE_REMOVED = [
* Params to be extracted due to correct usage
* of openlayers image wms
*/
const PARAMS_TO_BE_EXTRACTED = [
LAYERS_EXPRESSION,
STYLES_EXPRESSION,
VERSION_EXPRESSION,
REQUEST_EXPRESSION,
WMS_SERVICE_EXPRESSION,
Expand All @@ -30,15 +32,6 @@ const PARAMS_TO_BE_REMOVED = [
FORMAT_EXPRESSION
];

/**
* Params to be extracted due to correct usage
* of openlayers image wms
*/
const PARAMS_TO_BE_EXTRACTED = [
LAYERS_EXPRESSION,
STYLES_EXPRESSION
];


export const WMS_URL_VALIDATORS = [
/* OPTIONAL */
Expand All @@ -55,19 +48,6 @@ export const WMS_URL_VALIDATORS = [
formatValidator
];

export function removeAutoGeneratedParams(url: string): string | null {
if (typeof url !== "string") {
return null;
}

let preparedUrl = url;
PARAMS_TO_BE_REMOVED
.forEach(regex => preparedUrl = preparedUrl.replace(regex, ''));
preparedUrl = preparedUrl.replace(/(&?)$/, '');

return preparedUrl;
}

export function getBaseUrlAndParamsFrom(overlay: Partial<Overlay>): {url: string, [param: string]: any} {
const urlBaseAndParams = overlay.url.split('?');
if (urlBaseAndParams.length === 1) {
Expand Down Expand Up @@ -160,7 +140,10 @@ function _extractParamsMapFrom(partialUrl: string) {
PARAMS_TO_BE_EXTRACTED
.map(regex => partialUrl.match(regex))
.filter(match => !!match && match.length > 0)
.map(match => match[0].replace('&', '').split('='))
.map(match => match[0]
.replace('&', '')
.split('=')
)
.forEach(([key, value]) => extractedParams[key] = value);

return extractedParams;
Expand Down
10 changes: 4 additions & 6 deletions s4e-web/src/app/state/session/session.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,7 @@ export class SessionService {
.pipe(
handleHttpRequest$(this._store),
switchMap(data => this._profileLoaderService.loadProfile$()),
tap(() => this._store.update({email: request.email})),
finalize(() => this._navigateToApplication())
tap(() => this._store.update({email: request.email}))
);
}

Expand All @@ -83,10 +82,9 @@ export class SessionService {
this._http.post(`${environment.apiPrefixV1}/logout`, {})
.pipe(
tap(() => resetStores()),
tap(() => this._store.reset()),
finalize(() => this._router.navigate(['/login']))
tap(() => this._store.reset())
)
.subscribe();
.subscribe(() => this._router.navigate(['/login']));
}

removeAccount$(email: string, password: string) {
Expand All @@ -107,7 +105,7 @@ export class SessionService {
this._store.setError(null);
}

private _navigateToApplication() {
goToLastUrl() {
(
!!this._backLink && this._backLink !== ''
? this._router.navigateByUrl(this._backLink)
Expand Down
9 changes: 5 additions & 4 deletions s4e-web/src/app/views/login/login.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,12 @@ export class LoginComponent extends GenericFormComponent<SessionQuery, LoginForm
.pipe(
untilDestroyed(this),
switchMap(() => this._activatedRoute.queryParamMap),
filter(params => params.has(TOKEN_QUERY_PARAMETER)),
map(params => params.get(TOKEN_QUERY_PARAMETER)),
tap(token => this._invitationService.confirm(token))
tap(params => params.has(TOKEN_QUERY_PARAMETER)
? this._invitationService.confirm(params.get(TOKEN_QUERY_PARAMETER))
: null
)
)
.subscribe();
.subscribe(() => this._sessionService.goToLastUrl());
}

protected _loadBackLink() {
Expand Down
2 changes: 2 additions & 0 deletions s4e-web/src/app/views/map-view/map/map.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ export class MapComponent implements OnInit, OnDestroy {
}

private _clearPolygonDrawing() {
this._sentinelSearchService.setFootprint(null);

if (!!this._polygonDrawing.layer) {
this.map.removeLayer(this._polygonDrawing.layer);
this._polygonDrawing.layer.getSource()
Expand Down
26 changes: 15 additions & 11 deletions s4e-web/src/app/views/map-view/state/overlay/image-wms.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,26 @@ import { getImageXhr } from 'src/app/views/settings/manage-institutions/institut
import ImageWrapper from 'ol/Image';


export function getImageWmsLoader(
error$: Subscriber<string>,
urlParser: (src: string) => string = (src: string) => src
) {
export function getImageWmsLoader(error$: Subscriber<string>) {
return (image: ImageWrapper, src: string) => {
handleBrowserEncodingError(image, error$);
return getHandledXhr(getImageXhr(urlParser(src)), error$);

const xhr = getHandledXhr(getImageXhr(src), error$);
xhr.onloadend = () => {
error$.next(src);
error$.complete();
};

return xhr;
};
}


function getHandledXhr(xhr: XMLHttpRequest, error$: Subscriber<string>) {
xhr.onload = () => !handleHttpError(xhr, error$)
|| handleHttpResponseImageTypeError(xhr, error$);
xhr.onloadend = () => error$.next(null);
xhr.onload = () => {
handleHttpError(xhr, error$);
handleHttpResponseImageTypeError(xhr, error$);
}
xhr.onerror = () => handleHttpCorsAndOtherErrors(xhr, error$);

xhr.send();
Expand Down Expand Up @@ -46,17 +51,16 @@ function handleBrowserEncodingError(image: ImageWrapper, error$: Subscriber<stri
}
}

function handleHttpError(xhr: XMLHttpRequest, error$: Subscriber<string>): boolean {
function handleHttpError(xhr: XMLHttpRequest, error$: Subscriber<string>) {
const errorMessage = getErrorMessageBy(xhr.status);
if (!!errorMessage) {
error$.error(errorMessage + ', sprawdź poprawność URL');
}

return !!errorMessage;
}

function handleHttpResponseImageTypeError(xhr: XMLHttpRequest, error$: Subscriber<string>) {
const isImage = xhr.getResponseHeader('content-type').indexOf('image') > -1;

const isInvalidImage = xhr.status === 200 && !isImage;
if (isInvalidImage) {
error$.error(`Odpowiedź serwera nie jest zdjęciem, sprawdź poprawność URL!`);
Expand Down
3 changes: 0 additions & 3 deletions s4e-web/src/app/views/map-view/state/overlay/overlay.model.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {Layer, Tile} from 'ol/layer';
import {IUILayer} from '../common.model';
import {EntityState} from '@datorama/akita';
import { getBaseUrlAndParamsFrom } from '../../view-manager/overlay-list-modal/wms-url.utils';
import { NgxUiLoaderService } from 'ngx-ui-loader';
import {TileLoader} from '../utils/layers-loader.util';

export const GLOBAL_OWNER_TYPE = 'GLOBAL';
export const INSTITUTIONAL_OWNER_TYPE = 'INSTITUTIONAL';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Overlay, UIOverlay } from './overlay.model';
import { getBaseUrlAndParamsFrom } from '../../view-manager/overlay-list-modal/wms-url.utils';
import {ImageWMS, TileWMS} from 'ol/source';
import {InjectorModule} from '../../../../common/injector.module';
import {TileLoader} from '../utils/layers-loader.util';
import {NgxUiLoaderService} from 'ngx-ui-loader';
import {Tile} from 'ol/layer';
import {getBaseUrlAndParamsFrom} from '../../../../components/overlay-list/wms-url.utils';

export function convertToUIOverlay(
overlay: Overlay,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,6 @@ export class ProductService {
)),
map(productsLicenses => productsLicenses
.filter(productLicenses => !!productLicenses && productLicenses.length > 0)
.map(licenses => {
console.log(licenses)
return licenses;
})
.map(productLicenses => ({
...productLicenses[0],
institutionsSlugs: productLicenses.map(license => license.institutionSlug)
Expand Down
Loading

0 comments on commit de38b29

Please sign in to comment.