Skip to content

Commit

Permalink
feat(geo): update queryUrl with min max resolution (#1167)
Browse files Browse the repository at this point in the history
* feat(geo): update queryUrl with min max resolution

* solve lint errors

* solve lint errors..

* solve lint errors

* test query

* test

* fix(query): fix query url bbox param

* wip

* branch testing new code

* merge with queryUrlFix

* add resolution and scale check

* lint

* check scale and resolution are exists

* verification of resolution and scale

* refactor/fix(queryUrl): fix conditions and examples for scale and resolution

* fix typo

* fix(query): fix htmlgml query geom

* fix(query): htmlgml2 geometries were undefined or wrong

---------

Co-authored-by: Philippe Lafreniere <[email protected]>
Co-authored-by: Pierre-Étienne Lord <[email protected]>
  • Loading branch information
3 people authored and cbourget committed Mar 21, 2023
1 parent 68535b9 commit 0a2054c
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 57 deletions.
45 changes: 35 additions & 10 deletions demo/src/app/geo/query/query.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export class AppQueryComponent {
private dataSourceService: DataSourceService,
private layerService: LayerService
) {

this.dataSourceService
.createAsyncDataSource({
type: 'osm'
Expand Down Expand Up @@ -104,6 +105,24 @@ export class AppQueryComponent {
);
});

this.layerService
.createAsyncLayer({
title: 'Query url test',
visible: true,
sourceOptions: {
type: 'wms',
url: 'https://geoegl.msp.gouv.qc.ca/apis/wss/all.fcgi',
queryable: true,
queryUrls: [{url: 'https://geoegl.msp.gouv.qc.ca/apis/wss/amenagement.fcgi?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=wms_mern_reg_admin&LAYERS=wms_mern_reg_admin&INFO_FORMAT=application/geojson&FEATURE_COUNT=20&I=50&J=50&CRS=EPSG%3A3857&STYLES=&WIDTH=101&HEIGHT=101&BBOX={bbox}'}],
queryLayerFeatures: false,
queryFormat: 'geojson',
params: {
layers: 'SDA_MUNIC_S_20K',
version: '1.3.0'
}
}
} as any).subscribe(l => this.map.addLayer(l));

this.dataSourceService
.createAsyncDataSource({
type: 'vector',
Expand Down Expand Up @@ -162,12 +181,13 @@ export class AppQueryComponent {
visible: true,
sourceOptions: {
type: 'mvt',
url:
'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/ne:ne_10m_admin_0_countries@EPSG:900913@pbf/{z}/{x}/{-y}.pbf',
url: 'https://ahocevar.com/geoserver/gwc/service/tms/1.0.0/ne:ne_10m_admin_0_countries@EPSG:900913@pbf/{z}/{x}/{-y}.pbf',
queryable: true,
queryUrl: 'https://geoegl.msp.gouv.qc.ca/apis/wss/amenagement.fcgi?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=wms_mern_reg_admin&LAYERS=wms_mern_reg_admin&INFO_FORMAT=application%2Fgeojson&FEATURE_COUNT=20&I=50&J=50&CRS=EPSG%3A3857&STYLES=&WIDTH=101&HEIGHT=101&BBOX={bbox}',
queryUrls: [
{ url: 'https://geoegl.msp.gouv.qc.ca/apis/wss/amenagement.fcgi?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=wms_mern_reg_admin&LAYERS=wms_mern_reg_admin&INFO_FORMAT=application%2Fgeojson&FEATURE_COUNT=20&I=50&J=50&CRS=EPSG%3A3857&STYLES=&WIDTH=101&HEIGHT=101&BBOX={bbox}'}
],
queryLayerFeatures: false,
queryFormat: 'geojson'
// queryFormat: 'geojson'
},
mapboxStyle: {
url: 'assets/mapboxStyleExample-vectortile.json',
Expand All @@ -176,17 +196,24 @@ export class AppQueryComponent {
} as any)
.subscribe(l => this.map.addLayer(l));


this.dataSourceService
.createAsyncDataSource({
type: 'wms',
url: 'https://geoegl.msp.gouv.qc.ca/apis/wss/incendie.fcgi',
queryable: true,
queryUrl: 'https://geoegl.msp.gouv.qc.ca/apis/wss/amenagement.fcgi?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=SDA_MUNIC_S_20K&LAYERS=SDA_MUNIC_S_20K&INFO_FORMAT=geojson&FEATURE_COUNT=20&I=50&J=50&CRS=EPSG%3A3857&STYLES=&WIDTH=101&HEIGHT=101&BBOX={bbox}',
queryUrls: [
{
url:'https://geoegl.msp.gouv.qc.ca/apis/wss/amenagement.fcgi?SERVICE=WMS&VERSION=1.3.0&REQUEST=GetFeatureInfo&FORMAT=image%2Fpng&TRANSPARENT=true&QUERY_LAYERS=SDA_MUNIC_S_20K&LAYERS=SDA_MUNIC_S_20K&INFO_FORMAT=geojson&FEATURE_COUNT=20&I=50&J=50&CRS=EPSG%3A3857&STYLES=&WIDTH=101&HEIGHT=101&BBOX={bbox}',
// minScale: 20000,
// maxScale: 9000000,
minResolution: 0,
maxResolution: 400
}
],
queryFormat: 'geojson',
params: {
layers: 'MSP_CASERNE_PUBLIC',
version: '1.3.0'
version: '1.3.0',
layers: 'MSP_CASERNE_PUBLIC'
}
} as QueryableDataSourceOptions)
.subscribe(dataSource => {
Expand All @@ -198,7 +225,6 @@ export class AppQueryComponent {
})
);
});

}

handleQueryResults(results) {
Expand All @@ -207,7 +233,6 @@ export class AppQueryComponent {
this.features$.next(features);
this.map.queryResultsOverlay.setFeatures(features, FeatureMotion.None);
}

}

getTitle(result: SearchResult) {
Expand Down
10 changes: 9 additions & 1 deletion packages/geo/src/lib/query/shared/query.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export interface QueryableDataSourceOptions extends DataSourceOptions {
queryable?: boolean;
queryFormat?: QueryFormat;
queryTitle?: string;
queryUrl?: string;
queryUrls?: QueryUrlData[];
queryLayerFeatures?: boolean;
mapLabel?: string;
queryHtmlTarget?: QueryHtmlTarget;
Expand All @@ -31,3 +31,11 @@ export interface QueryableDataSource extends DataSource {
queryHtmlTarget?: QueryHtmlTarget;
options: QueryableDataSourceOptions;
}

export interface QueryUrlData {
url: string;
maxResolution?: number;
minResolution?: number;
maxScale?: number;
minScale?: number;
}
208 changes: 162 additions & 46 deletions packages/geo/src/lib/query/shared/query.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ import {
import {
QueryOptions,
QueryableDataSource,
QueryableDataSourceOptions
QueryableDataSourceOptions,
QueryUrlData
} from './query.interfaces';
import { MapExtent } from '../../map/shared/map.interface';

Expand All @@ -59,40 +60,126 @@ export class QueryService {
this.messageService.remove(id);
});
}
return layers
.filter((layer: Layer) => layer.visible && layer.isInResolutionsRange)
.map((layer: Layer) => this.queryLayer(layer, options));

const newLayers = layers.filter((layer: Layer) => layer.visible && layer.isInResolutionsRange)
.map((layer: Layer) => this.queryLayer(layer, options));
// the directive accept array in this format [observable, observable...]
// if we use multiple 'url' in queryUrl so the result => this form [observable, observable, [observable, observable]]
// so we need to flat the array
let flatArray = [].concat.apply([], newLayers);
return flatArray;
}

queryLayer(layer: Layer, options: QueryOptions): Observable<Feature[]> {
queryLayer(layer: Layer, options: QueryOptions): Observable<Feature[]> | Observable<Feature[]>[] {
const url = this.getQueryUrl(layer.dataSource, options, false, layer.map.viewController.getExtent());
if (!url) {
return of([]);
}

if (
(layer.dataSource as QueryableDataSource).options.queryFormat ===
QueryFormat.HTMLGML2
) {
const urlGml = this.getQueryUrl(layer.dataSource, options, true);
return this.http.get(urlGml, { responseType: 'text' }).pipe(
mergeMap(gmlRes => {
const mergedGML = this.mergeGML(gmlRes, url, layer);
const imposedGeom = mergedGML[0];
const imposedProperties = mergedGML[1];
return this.http
.get(url, { responseType: 'text' })
.pipe(
map(res =>
this.extractData(res, layer, options, url, imposedGeom, imposedProperties)
)
);
})
);
const resolution: number = layer.map.viewController.getResolution();
const scale: number = layer.map.viewController.getScale();

if ((layer.dataSource as QueryableDataSource).options.queryFormat === QueryFormat.HTMLGML2) {
if (typeof url === 'string') {
const urlGml = this.getQueryUrl(layer.dataSource, options, true) as string;
return this.http.get(urlGml, { responseType: 'text' }).pipe(
mergeMap(gmlRes => {
const mergedGML = this.mergeGML(gmlRes, url, layer);
const imposedGeom = mergedGML[0];
const imposedProperties = mergedGML[1];
return this.http
.get(url, { responseType: 'text' })
.pipe(
map(res =>
this.extractData(res, layer, options, url, imposedGeom, imposedProperties)
)
);
})
);
}
const urlGmls = this.getQueryUrl(layer.dataSource, options, true);
let observables: any = [];
for (let i = 0; i < urlGmls.length; i++) {
const element = urlGmls[i] as QueryUrlData;
if (this.checkScaleAndResolution(resolution, scale, element)) {
observables.push(this.requestDataForHTMLGML2(element.url, url[i].url, layer, options));
}
}
return observables;
} else {
if (typeof url === 'string') {
const request = this.http.get(url, { responseType: 'text' });
return request.pipe(map(res => this.extractData(res, layer, options, url)));
}
let observables: any = [];
for (let i = 0; i < url.length; i++) {
const element: QueryUrlData = url[i];
if (this.checkScaleAndResolution(resolution, scale, element)) {
const request = this.http.get(element.url, { responseType: 'text' });
observables.push(request.pipe(map(res => this.extractData(res, layer, options, element.url))));
}
}
return observables;
}
}

const request = this.http.get(url, { responseType: 'text' });
return request.pipe(map(res => this.extractData(res, layer, options, url)));
private requestDataForHTMLGML2(urlGml: string, url: string ,layer: Layer, options: QueryOptions): Observable<Feature[]> {
return this.http.get(urlGml, { responseType: 'text' }).pipe(
mergeMap(gmlRes => {
const mergedGML = this.mergeGML(gmlRes, url, layer);
const imposedGeom = mergedGML[0];
const imposedProperties = mergedGML[1];
return this.http
.get(url, { responseType: 'text' })
.pipe(
map(res =>
this.extractData(res, layer, options, url, imposedGeom, imposedProperties)
)
);
})
);
}

private checkScaleAndResolution(resolution: number, scale: number, element: QueryUrlData): boolean {
let checkScale: boolean;
let checkResolution: boolean;

if (!element.minResolution && !element.maxResolution && !element.minScale && !element.maxScale) {
return true;
} else {
/******************* checking Resolution *******************/
if (element.minResolution && element.maxResolution) {
// if "minResolution" and "maxResolution" exists check if resoltion is between
if ((resolution >= element.minResolution) && (resolution <= element.maxResolution)) {
checkResolution = true;
}
} else {
// check if "minResolution" or "maxResolution" exists
if (element.minResolution && resolution >= element.minResolution) { checkResolution = true; }
if (element.maxResolution && resolution <= element.maxResolution) { checkResolution = true; }
}

/******************* checking Scale *******************/
if (element.minScale && element.maxScale) {
// if "minScale" and "maxScale" exists check if scale is between
if ((scale > element.minScale) && (scale < element.maxScale)) {
checkScale = true;
}
} else {
// check if "minScale" or "maxScale" exists
if (element.maxScale && scale <= element.maxScale) { checkScale = true; }
if (element.minScale && scale >= element.minScale) { checkScale = true; }
}

/******************* result of checking *******************/
if (checkScale === true && checkResolution === true) {
return true;
} else if ((checkResolution === true && !checkScale) || (checkScale === true && !checkResolution)) {
return true;
} else {
return false;
}
}
}

private mergeGML(gmlRes, url, layer: Layer): [FeatureGeometry, { [key: string]: any }] {
Expand Down Expand Up @@ -193,18 +280,22 @@ export class QueryService {
type: olmline.getType(),
coordinates: olmline.getCoordinates()
};
break;
case 'Point':
returnGeometry = olmpts;
break;
case 'Polygon':
returnGeometry = {
type: olmpoly.getType(),
coordinates: olmpoly.getCoordinates()
};
break;
case 'MultiPolygon':
returnGeometry = {
type: olmpoly.getType(),
coordinates: olmpoly.getCoordinates()
};
break;
}
const imposedProperties = {};

Expand Down Expand Up @@ -600,10 +691,10 @@ export class QueryService {
options: QueryOptions,
forceGML2 = false,
mapExtent?: MapExtent
): string {
): string | QueryUrlData[]{
let url;

if (datasource.options.queryUrl) {
if (datasource.options.queryUrls) {
return this.getCustomQueryUrl(datasource, options, mapExtent);
}

Expand Down Expand Up @@ -781,25 +872,50 @@ export class QueryService {
getCustomQueryUrl(
datasource: QueryableDataSource,
options: QueryOptions,
mapExtent?: MapExtent): string {
mapExtent?: MapExtent): QueryUrlData[] {

const extent = olextent.getForViewAndSize(
options.coordinates,
options.resolution,
0,
[101, 101]
);
const extent = olextent.getForViewAndSize(
options.coordinates,
options.resolution,
0,
[101, 101]
);

let url = datasource.options.queryUrl.replace(/\{bbox\}/g, extent.join(','))
.replace(/\{xmin\}/g, mapExtent[0].toString())
.replace(/\{ymin\}/g, mapExtent[1].toString())
.replace(/\{xmax\}/g, mapExtent[2].toString())
.replace(/\{ymax\}/g, mapExtent[3].toString())
.replace(/\{x\}/g, options.coordinates[0].toString())
.replace(/\{y\}/g, options.coordinates[1].toString())
.replace(/\{resolution\}/g, options.resolution.toString())
.replace(/\{srid\}/g, options.projection.replace('EPSG:',''));
return datasource.options.queryUrls.map(item => {
let data: QueryUrlData = {
url: item.url.replace(/\{bbox\}/g, extent.join(','))
.replace(/\{x\}/g, options.coordinates[0].toString())
.replace(/\{y\}/g, options.coordinates[1].toString())
.replace(/\{resolution\}/g, options.resolution.toString())
.replace(/\{srid\}/g, options.projection.replace('EPSG:',''))
};

return url;
}
// if the queryFormat changed to "QueryFormat.HTMLGML2": mapExtent will be undefined
// we need to check "mapExtent" befor replace variables in the url
if(mapExtent) {
data.url.replace(/\{xmin\}/g, mapExtent[0].toString())
.replace(/\{ymin\}/g, mapExtent[1].toString())
.replace(/\{xmax\}/g, mapExtent[2].toString())
.replace(/\{ymax\}/g, mapExtent[3].toString());
}

if(item.maxResolution) {
data.maxResolution = item.maxResolution;
}

if(item.minResolution) {
data.minResolution = item.minResolution;
}

if(item.minScale) {
data.minScale = item.minScale;
}

if(item.maxScale) {
data.maxScale = item.maxScale;
}

return data;
});
}
}

0 comments on commit 0a2054c

Please sign in to comment.