diff --git a/packages/context/src/lib/share-map/shared/share-map.service.ts b/packages/context/src/lib/share-map/shared/share-map.service.ts index 49469ab174..cb40b0f757 100644 --- a/packages/context/src/lib/share-map/shared/share-map.service.ts +++ b/packages/context/src/lib/share-map/shared/share-map.service.ts @@ -103,8 +103,8 @@ export class ShareMapService { let zoom = 'zoom=' + map.viewController.getZoom(); const arrayCenter = map.viewController.getCenter('EPSG:4326') || []; - const long = arrayCenter[0].toFixed(5).replace(/\.([^0]+)0+$/,".$1") - const lat = arrayCenter[1].toFixed(5).replace(/\.([^0]+)0+$/,".$1") + const long = arrayCenter[0].toFixed(5).replace(/\.([^0]+)0+$/, '.$1'); + const lat = arrayCenter[1].toFixed(5).replace(/\.([^0]+)0+$/, '.$1'); const center = `center=${long},${lat}`.replace(/.00000/g, ''); let context = ''; if (this.contextService.context$.value) { @@ -112,22 +112,25 @@ export class ShareMapService { context = 'context=' + this.contextService.context$.value.uri; } if (this.contextService.context$.value.map.view.zoom) { - zoom = this.contextService.context$.value.map.view.zoom === map.viewController.getZoom() ? '' : 'zoom=' + map.viewController.getZoom(); + zoom = + this.contextService.context$.value.map.view.zoom === + map.viewController.getZoom() + ? '' + : 'zoom=' + map.viewController.getZoom(); } } let url = `${location.origin}${ location.pathname - }?${context}&${zoom}&${center}&${layersUrl}&${llc}&${routingUrl}` + }?${context}&${zoom}&${center}&${layersUrl}&${llc}&${routingUrl}`; for (let i = 0; i < 5; i++) { url = url.replace(/&&/g, '&'); url = url.endsWith('&') ? url.slice(0, -1) : url; } url = url.endsWith('&') ? url.slice(0, -1) : url; - url = url.replace('?&', '?') + url = url.replace('?&', '?'); return url; } - } diff --git a/packages/geo/src/lib/datasource/shared/datasource.service.ts b/packages/geo/src/lib/datasource/shared/datasource.service.ts index b602b92060..8d7ecc7534 100644 --- a/packages/geo/src/lib/datasource/shared/datasource.service.ts +++ b/packages/geo/src/lib/datasource/shared/datasource.service.ts @@ -27,7 +27,9 @@ import { WebSocketDataSource, AnyDataSourceOptions, MVTDataSource, - MVTDataSourceOptions + MVTDataSourceOptions, + ClusterDataSource, + ClusterDataSourceOptions } from './datasources'; @Injectable({ @@ -86,15 +88,18 @@ export class DataSourceService { ); break; case 'mvt': - dataSource = this.createMVTDataSource( - context as MVTDataSourceOptions - ); + dataSource = this.createMVTDataSource(context as MVTDataSourceOptions); break; case 'tilearcgisrest': dataSource = this.createTileArcGISRestDataSource( context as TileArcGISRestDataSourceOptions ); break; + case 'cluster': + dataSource = this.createClusterDataSource( + context as ClusterDataSourceOptions + ); + break; default: console.error(context); throw new Error('Invalid datasource type'); @@ -208,9 +213,16 @@ export class DataSourceService { ) ); } + private createMVTDataSource( context: MVTDataSourceOptions ): Observable { return new Observable(d => d.next(new MVTDataSource(context))); } + + private createClusterDataSource( + context: ClusterDataSourceOptions + ): Observable { + return new Observable(d => d.next(new ClusterDataSource(context))); + } } diff --git a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts index c0224f574b..aa4d8c1a3f 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts @@ -9,6 +9,7 @@ import { CartoDataSourceOptions } from './carto-datasource.interface'; import { ArcGISRestDataSourceOptions } from './arcgisrest-datasource.interface'; import { TileArcGISRestDataSourceOptions } from './tilearcgisrest-datasource.interface'; import { MVTDataSourceOptions } from './mvt-datasource.interface'; +import { ClusterDataSourceOptions } from './cluster-datasource.interface'; export type AnyDataSourceOptions = | DataSourceOptions @@ -21,4 +22,5 @@ export type AnyDataSourceOptions = | CartoDataSourceOptions | ArcGISRestDataSourceOptions | TileArcGISRestDataSourceOptions - | MVTDataSourceOptions; + | MVTDataSourceOptions + | ClusterDataSourceOptions; diff --git a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts index 338fe925a0..616f9accae 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/any-datasource.ts @@ -10,6 +10,7 @@ import { ArcGISRestDataSource } from './arcgisrest-datasource'; import { TileArcGISRestDataSource } from './tilearcgisrest-datasource'; import { WebSocketDataSource } from './websocket-datasource'; import { MVTDataSource } from './mvt-datasource'; +import { ClusterDataSource } from './cluster-datasource'; export type AnyDataSource = | DataSource @@ -23,4 +24,5 @@ export type AnyDataSource = | ArcGISRestDataSource | TileArcGISRestDataSource | WebSocketDataSource - | MVTDataSource; + | MVTDataSource + | ClusterDataSource; diff --git a/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts new file mode 100644 index 0000000000..180147b9a3 --- /dev/null +++ b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.interface.ts @@ -0,0 +1,11 @@ +import olSourceVector from 'ol/source/Vector'; + +import { FeatureDataSource } from './feature-datasource'; +import { FeatureDataSourceOptions } from './feature-datasource.interface'; + +export interface ClusterDataSourceOptions extends FeatureDataSourceOptions { + // type?: 'cluster'; + distance?: number; + source?: FeatureDataSource; + ol?: olSourceVector; +} diff --git a/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts new file mode 100644 index 0000000000..4f3a0925b1 --- /dev/null +++ b/packages/geo/src/lib/datasource/shared/datasources/cluster-datasource.ts @@ -0,0 +1,21 @@ +import olSourceCluster from 'ol/source/Cluster'; + +import { uuid } from '@igo2/utils'; + +import { FeatureDataSource } from './feature-datasource'; +import { ClusterDataSourceOptions } from './cluster-datasource.interface'; + +export class ClusterDataSource extends FeatureDataSource { + public options: ClusterDataSourceOptions; + public ol: olSourceCluster; + + protected createOlSource(): olSourceCluster { + this.options.format = this.getSourceFormatFromOptions(this.options); + this.options.source = super.createOlSource(); + return new olSourceCluster(this.options); + } + + protected generateId() { + return uuid(); + } +} diff --git a/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts b/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts index e3f24cc3d1..6ecc901281 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/datasource.interface.ts @@ -1,6 +1,4 @@ import olSource from 'ol/source/Source'; - -import { DataSource } from './datasource'; import { DownloadOptions } from '../../../download/shared/download.interface'; export interface DataSourceOptions { @@ -15,7 +13,8 @@ export interface DataSourceOptions { | 'arcgisrest' | 'tilearcgisrest' | 'websocket' - | 'mvt'; + | 'mvt' + | 'cluster'; legend?: DataSourceLegendOptions; optionsFromCapabilities?: boolean; // title: string; diff --git a/packages/geo/src/lib/datasource/shared/datasources/index.ts b/packages/geo/src/lib/datasource/shared/datasources/index.ts index ea7a9f82b7..6ce8d08533 100644 --- a/packages/geo/src/lib/datasource/shared/datasources/index.ts +++ b/packages/geo/src/lib/datasource/shared/datasources/index.ts @@ -24,5 +24,7 @@ export * from './websocket-datasource'; export * from './websocket-datasource.interface'; export * from './mvt-datasource'; export * from './mvt-datasource.interface'; +export * from './cluster-datasource'; +export * from './cluster-datasource.interface'; export * from './any-datasource'; export * from './any-datasource.interface'; diff --git a/packages/geo/src/lib/layer/shared/clusterParam.ts b/packages/geo/src/lib/layer/shared/clusterParam.ts new file mode 100644 index 0000000000..989a69f6ef --- /dev/null +++ b/packages/geo/src/lib/layer/shared/clusterParam.ts @@ -0,0 +1,5 @@ +export interface ClusterParam { + clusterRange?: Array; // utiliser lorsqu'on veux une symbologie active pour une source cluster. + clusterIcon?: string; + clusterScale?: number; +} diff --git a/packages/geo/src/lib/layer/shared/layer.service.ts b/packages/geo/src/lib/layer/shared/layer.service.ts index 409b418055..da5723d74b 100644 --- a/packages/geo/src/lib/layer/shared/layer.service.ts +++ b/packages/geo/src/lib/layer/shared/layer.service.ts @@ -16,7 +16,8 @@ import { ArcGISRestDataSource, TileArcGISRestDataSource, WebSocketDataSource, - MVTDataSource + MVTDataSource, + ClusterDataSource } from '../../datasource'; import { DataSourceService } from '../../datasource/shared/datasource.service'; @@ -62,7 +63,8 @@ export class LayerService { layerOptions.source.options.optionsFromCapabilities ) { layerOptions = ObjectUtils.mergeDeep( - (layerOptions.source.options as any)._layerOptionsFromCapabilities || {}, + (layerOptions.source.options as any)._layerOptionsFromCapabilities || + {}, layerOptions || {} ); } @@ -80,13 +82,16 @@ export class LayerService { case WFSDataSource: case ArcGISRestDataSource: case WebSocketDataSource: + case ClusterDataSource: layer = this.createVectorLayer(layerOptions as VectorLayerOptions); break; case WMSDataSource: layer = this.createImageLayer(layerOptions as ImageLayerOptions); break; case MVTDataSource: - layer = this.createVectorTileLayer(layerOptions as VectorTileLayerOptions); + layer = this.createVectorTileLayer( + layerOptions as VectorTileLayerOptions + ); break; default: break; @@ -131,11 +136,24 @@ export class LayerService { if (layerOptions.source instanceof ArcGISRestDataSource) { const source = layerOptions.source as ArcGISRestDataSource; style = source.options.params.style; - } else if (layerOptions.styleByAttribute) { const serviceStyle = this.styleService; - layerOptions.style = (feature) => { - return serviceStyle.createStyleByAttribute(feature, layerOptions.styleByAttribute); + layerOptions.style = feature => { + return serviceStyle.createStyleByAttribute( + feature, + layerOptions.styleByAttribute + ); + }; + return new VectorLayer(layerOptions); + } + + if (layerOptions.source instanceof ClusterDataSource) { + const serviceStyle = this.styleService; + layerOptions.style = feature => { + return serviceStyle.createClusterStyle( + feature, + layerOptions.clusterParam + ); }; return new VectorLayer(layerOptions); } @@ -147,7 +165,9 @@ export class LayerService { return new VectorLayer(layerOptionsOl); } - private createVectorTileLayer(layerOptions: VectorTileLayerOptions): VectorTileLayer { + private createVectorTileLayer( + layerOptions: VectorTileLayerOptions + ): VectorTileLayer { let style; if (layerOptions.style !== undefined) { style = this.styleService.createStyle(layerOptions.style); @@ -155,8 +175,11 @@ export class LayerService { if (layerOptions.styleByAttribute) { const serviceStyle = this.styleService; - layerOptions.style = (feature) => { - return serviceStyle.createStyleByAttribute(feature, layerOptions.styleByAttribute); + layerOptions.style = feature => { + return serviceStyle.createStyleByAttribute( + feature, + layerOptions.styleByAttribute + ); }; return new VectorTileLayer(layerOptions); } diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts index f4c24ee579..eaf2dd4cab 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.interface.ts @@ -8,11 +8,15 @@ import { FeatureDataSource } from '../../../datasource/shared/datasources/featur import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arcgisrest-datasource'; import { WebSocketDataSource } from '../../../datasource/shared/datasources/websocket-datasource'; +import { ClusterDataSource } from '../../../datasource/shared/datasources/cluster-datasource'; import { FeatureDataSourceOptions } from '../../../datasource/shared/datasources/feature-datasource.interface'; import { WFSDataSourceOptions } from '../../../datasource/shared/datasources/wfs-datasource.interface'; import { ArcGISRestDataSourceOptions } from '../../../datasource/shared/datasources/arcgisrest-datasource.interface'; import { WebSocketDataSourceOptions } from '../../../datasource/shared/datasources/websocket-datasource.interface'; +import { ClusterDataSourceOptions } from '../../../datasource/shared/datasources/cluster-datasource.interface'; + +import { ClusterParam } from '../clusterParam'; import { StyleByAttribute } from '../stylebyattribute'; @@ -21,18 +25,21 @@ export interface VectorLayerOptions extends LayerOptions { | FeatureDataSource | WFSDataSource | ArcGISRestDataSource - | WebSocketDataSource; + | WebSocketDataSource + | ClusterDataSource; sourceOptions?: | FeatureDataSourceOptions | WFSDataSourceOptions | ArcGISRestDataSourceOptions - | WebSocketDataSourceOptions; + | WebSocketDataSourceOptions + | ClusterDataSourceOptions; style?: { [key: string]: any } | olStyle | olStyle[]; browsable?: boolean; exportable?: boolean; ol?: olLayerVector; animation?: VectorAnimation; styleByAttribute?: StyleByAttribute; + clusterParam?: ClusterParam; } export interface VectorAnimation { diff --git a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts index dbeecf2cb1..597c69aac7 100644 --- a/packages/geo/src/lib/layer/shared/layers/vector-layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/vector-layer.ts @@ -8,16 +8,13 @@ import { FeatureDataSource } from '../../../datasource/shared/datasources/featur import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arcgisrest-datasource'; import { WebSocketDataSource } from '../../../datasource/shared/datasources/websocket-datasource'; +import { ClusterDataSource } from '../../../datasource/shared/datasources/cluster-datasource'; import { Layer } from './layer'; import { VectorLayerOptions } from './vector-layer.interface'; export class VectorLayer extends Layer { - public dataSource: - | FeatureDataSource - | WFSDataSource - | ArcGISRestDataSource - | WebSocketDataSource; + public dataSource: FeatureDataSource | WFSDataSource | ArcGISRestDataSource | WebSocketDataSource | ClusterDataSource; public options: VectorLayerOptions; public ol: olLayerVector; diff --git a/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts b/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts index 2bf337bb4f..d950cca00e 100644 --- a/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts +++ b/packages/geo/src/lib/layer/shared/layers/vectortile-layer.ts @@ -7,8 +7,7 @@ import { Layer } from './layer'; import { VectorTileLayerOptions } from './vectortile-layer.interface'; export class VectorTileLayer extends Layer { - public dataSource: - | MVTDataSource; + public dataSource: MVTDataSource; public options: VectorTileLayerOptions; public ol: olLayerVectorTile; @@ -19,11 +18,8 @@ export class VectorTileLayer extends Layer { protected createOlLayer(): olLayerVectorTile { const olOptions = Object.assign({}, this.options, { source: this.options.source.ol as olSourceVectorTile - }); return new olLayerVectorTile(olOptions); } } - - diff --git a/packages/geo/src/lib/layer/shared/style.service.ts b/packages/geo/src/lib/layer/shared/style.service.ts index ff1e1fbe8b..8ffb5b2ae0 100644 --- a/packages/geo/src/lib/layer/shared/style.service.ts +++ b/packages/geo/src/lib/layer/shared/style.service.ts @@ -3,6 +3,8 @@ import { Injectable } from '@angular/core'; import * as olstyle from 'ol/style'; import { StyleByAttribute } from './stylebyattribute'; +import { ClusterParam } from './clusterParam'; + @Injectable({ providedIn: 'root' }) @@ -51,7 +53,6 @@ export class StyleService { return olCls; } - createStyleByAttribute(feature, styleByAttribute: StyleByAttribute) { let style; const type = styleByAttribute.type; @@ -70,46 +71,53 @@ export class StyleService { for (let i = 0; i < size; i++) { if (feature.get(attribute) === data[i]) { if (icon) { - style = [new olstyle.Style({ - image: new olstyle.Icon({ - src: icon[i], - scale: scale ? scale[i] : 1 - }) - })]; - return style; + style = [ + new olstyle.Style({ + image: new olstyle.Icon({ + src: icon[i], + scale: scale ? scale[i] : 1 + }) + }) + ]; + return style; } - style = [new olstyle.Style({ + style = [ + new olstyle.Style({ + image: new olstyle.Circle({ + radius: radius ? radius[i] : 4, + stroke: new olstyle.Stroke({ + color: stroke ? stroke[i] : 'black' + }), + fill: new olstyle.Fill({ + color: fill ? fill[i] : 'black' + }) + }) + }) + ]; + return style; + } + } + if (!feature.getStyle()) { + style = [ + new olstyle.Style({ image: new olstyle.Circle({ - radius: radius ? radius[i] : 4, + radius: 4, stroke: new olstyle.Stroke({ - color: stroke ? stroke[i] : 'black' + color: 'black' }), fill: new olstyle.Fill({ - color: fill ? fill[i] : 'black' + color: '#bbbbf2' }) }) - })]; - return style; - } + }) + ]; + return style; } - if (!feature.getStyle()) { - style = [new olstyle.Style({ - image: new olstyle.Circle({ - radius: 4, - stroke: new olstyle.Stroke({ - color: 'black' - }), - fill: new olstyle.Fill({ - color: '#bbbbf2' - }) - }) - })]; - return style; - } - } else if (type === 'regular') { - for (let i = 0; i < size; i++) { - if (feature.get(attribute) === data[i]) { - style = [new olstyle.Style({ + } else if (type === 'regular') { + for (let i = 0; i < size; i++) { + if (feature.get(attribute) === data[i]) { + style = [ + new olstyle.Style({ stroke: new olstyle.Stroke({ color: stroke ? stroke[i] : 'black', width: width ? width[i] : 1 @@ -123,25 +131,93 @@ export class StyleService { color: 'black' }) }) - })]; - return style; - } - } - if (!feature.getStyle()) { - if (baseStyle) { - style = this.createStyle(baseStyle); - return style; - } - style = [new olstyle.Style({ + }) + ]; + return style; + } + } + if (!feature.getStyle()) { + if (baseStyle) { + style = this.createStyle(baseStyle); + return style; + } + style = [ + new olstyle.Style({ stroke: new olstyle.Stroke({ color: 'black' }), fill: new olstyle.Fill({ color: '#bbbbf2' }) - })]; - return style; + }) + ]; + return style; + } + } + } + + createClusterStyle(feature, clusterParam: ClusterParam) { + let style; + const range = clusterParam.clusterRange; + const icon = clusterParam.clusterIcon; + const scale = clusterParam.clusterScale; + const size = feature.get('features').length; + let color; + if (size !== 1) { + if (range) { + if (size >= range[1]) { + color = 'red'; + } else if (size < range[1] && size >= range[0]) { + color = 'orange'; + } else if (size < range[0]) { + color = 'green'; } - } - } + } + style = [ + new olstyle.Style({ + image: new olstyle.Circle({ + radius: 2 * size + 3.4, + stroke: new olstyle.Stroke({ + color: 'black' + }), + fill: new olstyle.Fill({ + color: range ? color : 'blue' + }) + }), + text: new olstyle.Text({ + text: size.toString(), + fill: new olstyle.Fill({ + color: '#fff' + }) + }) + }) + ]; + } else { + if (icon) { + style = [ + new olstyle.Style({ + image: new olstyle.Icon({ + src: icon, + scale + }) + }) + ]; + } else { + style = [ + new olstyle.Style({ + image: new olstyle.Circle({ + radius: 2 * size + 3.4, + stroke: new olstyle.Stroke({ + color: 'black' + }), + fill: new olstyle.Fill({ + color: 'blue' + }) + }) + }) + ]; + } + } + return style; + } }