diff --git a/projects/geo/src/lib/datasource/shared/capabilities.service.ts b/projects/geo/src/lib/datasource/shared/capabilities.service.ts index 37b979245b..4beb076ca4 100644 --- a/projects/geo/src/lib/datasource/shared/capabilities.service.ts +++ b/projects/geo/src/lib/datasource/shared/capabilities.service.ts @@ -1,14 +1,21 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; -import { Observable } from 'rxjs'; +import { Observable, forkJoin } from 'rxjs'; import { map } from 'rxjs/operators'; import { WMSCapabilities, WMTSCapabilities } from 'ol/format'; import olSourceWMTS from 'ol/source/WMTS'; +import olAttribution from 'ol/control/Attribution'; import { ObjectUtils } from '@igo2/utils'; +import { EsriStyleGenerator } from '../utils/esri-style-generator'; -import { WMTSDataSourceOptions, WMSDataSourceOptions } from './datasources'; +import { + WMTSDataSourceOptions, + WMSDataSourceOptions, + ArcGISRestDataSourceOptions, + TileArcGISRestDataSourceOptions +} from './datasources'; @Injectable({ providedIn: 'root' @@ -50,6 +57,35 @@ export class CapabilitiesService { return options; } + getArcgisOptions( + baseOptions: ArcGISRestDataSourceOptions + ): Observable { + const baseUrl = baseOptions.url + '/' + baseOptions.layer + '?f=json'; + + return this.http + .get(baseUrl) + .pipe( + map((arcgisOptions: any) => + this.parseArcgisOptions(baseOptions, arcgisOptions) + ) + ); + } + + getTileArcgisOptions( + baseOptions: TileArcGISRestDataSourceOptions + ): Observable { + const baseUrl = baseOptions.url + '/' + baseOptions.layer + '?f=json'; + const legendUrl = baseOptions.url + '/legend?f=json'; + const tileArcgisOptions = this.http.get(baseUrl); + const legendInfo = this.http.get(legendUrl); + + return forkJoin([tileArcgisOptions, legendInfo]).pipe( + map((res: any) => + this.parseTileArcgisOptions(baseOptions, res[0], res[1]) + ) + ); + } + getCapabilities( service: 'wms' | 'wmts', baseUrl: string, @@ -133,6 +169,87 @@ export class CapabilitiesService { return Object.assign(options, baseOptions); } + private parseArcgisOptions( + baseOptions: ArcGISRestDataSourceOptions, + arcgisOptions: any + ): ArcGISRestDataSourceOptions { + const styleGenerator = new EsriStyleGenerator(); + const units = arcgisOptions.units === 'esriMeters' ? 'm' : 'degrees'; + const style = styleGenerator.generateStyle(arcgisOptions, units); + const attributions = new olAttribution({ + html: arcgisOptions.copyrightText + }); + let timeExtent, timeFilter; + if (arcgisOptions.timeInfo) { + const time = arcgisOptions.timeInfo.timeExtent; + timeExtent = time[0] + ',' + time[1]; + const min = new Date(); + min.setTime(time[0]); + const max = new Date(); + max.setTime(time[1]); + timeFilter = { + min: min.toUTCString(), + max: max.toUTCString(), + range: true, + type: 'datetime', + style: 'calendar' + }; + } + const params = Object.assign( + {}, + { + style: style, + timeFilter: timeFilter, + timeExtent: timeExtent, + attributions: attributions + } + ); + const options = ObjectUtils.removeUndefined({ + params: params + }); + return ObjectUtils.mergeDeep(options, baseOptions); + } + + private parseTileArcgisOptions( + baseOptions: TileArcGISRestDataSourceOptions, + tileArcgisOptions: any, + legendInfo: any + ): TileArcGISRestDataSourceOptions { + const attributions = new olAttribution({ + html: tileArcgisOptions.copyrightText + }); + let timeExtent, timeFilter; + if (tileArcgisOptions.timeInfo) { + const time = tileArcgisOptions.timeInfo.timeExtent; + timeExtent = time[0] + ',' + time[1]; + const min = new Date(); + min.setTime(time[0]); + const max = new Date(); + max.setTime(time[1]); + timeFilter = { + min: min.toUTCString(), + max: max.toUTCString(), + range: true, + type: 'datetime', + style: 'calendar' + }; + } + const params = Object.assign( + {}, + { + layers: 'show:' + baseOptions.layer, + time: timeExtent + } + ); + const options = ObjectUtils.removeUndefined({ + params: params, + legendInfo: legendInfo, + timeFilter: timeFilter, + attributions: attributions + }); + return ObjectUtils.mergeDeep(options, baseOptions); + } + private findDataSourceInCapabilities(layerArray, name): any { if (Array.isArray(layerArray)) { let layer; diff --git a/projects/geo/src/lib/datasource/shared/datasource.service.ts b/projects/geo/src/lib/datasource/shared/datasource.service.ts index d1caf5ee5a..69953d2eaa 100644 --- a/projects/geo/src/lib/datasource/shared/datasource.service.ts +++ b/projects/geo/src/lib/datasource/shared/datasource.service.ts @@ -18,6 +18,12 @@ import { WMTSDataSourceOptions, WMSDataSource, WMSDataSourceOptions, + CartoDataSource, + CartoDataSourceOptions, + ArcGISRestDataSource, + ArcGISRestDataSourceOptions, + TileArcGISRestDataSource, + TileArcGISRestDataSourceOptions, AnyDataSourceOptions } from './datasources'; @@ -54,6 +60,21 @@ export class DataSourceService { case 'xyz': dataSource = this.createXYZDataSource(context as XYZDataSourceOptions); break; + case 'carto': + dataSource = this.createCartoDataSource( + context as CartoDataSourceOptions + ); + break; + case 'arcgisrest': + dataSource = this.createArcGISRestDataSource( + context as ArcGISRestDataSourceOptions + ); + break; + case 'tilearcgisrest': + dataSource = this.createTileArcGISRestDataSource( + context as TileArcGISRestDataSourceOptions + ); + break; default: break; } @@ -114,4 +135,36 @@ export class DataSourceService { ): Observable { return new Observable(d => d.next(new XYZDataSource(context))); } + + private createCartoDataSource( + context: CartoDataSourceOptions + ): Observable { + return new Observable(d => d.next(new CartoDataSource(context))); + } + + private createArcGISRestDataSource( + context: ArcGISRestDataSourceOptions + ): Observable { + return this.capabilitiesService + .getArcgisOptions(context) + .pipe( + map( + (options: ArcGISRestDataSourceOptions) => + new ArcGISRestDataSource(options) + ) + ); + } + + private createTileArcGISRestDataSource( + context: TileArcGISRestDataSourceOptions + ): Observable { + return this.capabilitiesService + .getTileArcgisOptions(context) + .pipe( + map( + (options: TileArcGISRestDataSourceOptions) => + new TileArcGISRestDataSource(options) + ) + ); + } } diff --git a/projects/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts b/projects/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts index cad2b0c480..9ce7f08ffc 100644 --- a/projects/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts +++ b/projects/geo/src/lib/datasource/shared/datasources/any-datasource.interface.ts @@ -5,6 +5,9 @@ import { WMSDataSourceOptions } from './wms-datasource.interface'; import { WMTSDataSourceOptions } from './wmts-datasource.interface'; import { WFSDataSourceOptions } from './wfs-datasource.interface'; import { FeatureDataSourceOptions } from './feature-datasource.interface'; +import { CartoDataSourceOptions } from './carto-datasource.interface'; +import { ArcGISRestDataSourceOptions } from './arcgisrest-datasource.interface'; +import { TileArcGISRestDataSourceOptions } from './tilearcgisrest-datasource.interface'; export type AnyDataSourceOptions = | DataSourceOptions @@ -13,4 +16,7 @@ export type AnyDataSourceOptions = | WFSDataSourceOptions | XYZDataSourceOptions | WMTSDataSourceOptions - | WMSDataSourceOptions; + | WMSDataSourceOptions + | CartoDataSourceOptions + | ArcGISRestDataSourceOptions + | TileArcGISRestDataSourceOptions; diff --git a/projects/geo/src/lib/datasource/shared/datasources/any-datasource.ts b/projects/geo/src/lib/datasource/shared/datasources/any-datasource.ts index a41b4f81d9..f581388ecd 100644 --- a/projects/geo/src/lib/datasource/shared/datasources/any-datasource.ts +++ b/projects/geo/src/lib/datasource/shared/datasources/any-datasource.ts @@ -5,6 +5,9 @@ import { WMSDataSource } from './wms-datasource'; import { WMTSDataSource } from './wmts-datasource'; import { WFSDataSource } from './wfs-datasource'; import { FeatureDataSource } from './feature-datasource'; +import { CartoDataSource } from './carto-datasource'; +import { ArcGISRestDataSource } from './arcgisrest-datasource'; +import { TileArcGISRestDataSource } from './tilearcgisrest-datasource'; export type AnyDataSource = | DataSource @@ -13,4 +16,7 @@ export type AnyDataSource = | WFSDataSource | XYZDataSource | WMTSDataSource - | WMSDataSource; + | WMSDataSource + | CartoDataSource + | ArcGISRestDataSource + | TileArcGISRestDataSource; diff --git a/projects/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.interface.ts b/projects/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.interface.ts new file mode 100644 index 0000000000..44e69820e6 --- /dev/null +++ b/projects/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.interface.ts @@ -0,0 +1,20 @@ +import olAttribution from 'ol/control/Attribution'; + +import { DataSourceOptions } from './datasource.interface'; +import { FeatureDataSourceOptions } from './feature-datasource.interface'; + +export interface ArcGISRestDataSourceOptions + extends DataSourceOptions, + FeatureDataSourceOptions { + // type?: 'arcgisrest'; + layer: string; + params?: ArcGISRestDataSourceOptionsParams; +} + +export interface ArcGISRestDataSourceOptionsParams { + legendInfo?: any; + style?: any; + timefilter?: any; + timeExtent?: string; + attributions?: olAttribution; +} diff --git a/projects/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts b/projects/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts new file mode 100644 index 0000000000..83b26cda2c --- /dev/null +++ b/projects/geo/src/lib/datasource/shared/datasources/arcgisrest-datasource.ts @@ -0,0 +1,56 @@ +import olSourceVector from 'ol/source/Vector'; +import olFormatEsriJSON from 'ol/format/EsriJSON'; +import * as olloadingstrategy from 'ol/loadingstrategy'; + +import { uuid } from '@igo2/utils'; + +import { DataSource } from './datasource'; +import { ArcGISRestDataSourceOptions } from './arcgisrest-datasource.interface'; + +export class ArcGISRestDataSource extends DataSource { + public ol: olSourceVector; + public options: ArcGISRestDataSourceOptions; + + protected createOlSource(): olSourceVector { + const esrijsonFormat = new olFormatEsriJSON(); + return new olSourceVector({ + attributions: this.options.params.attributions, + overlaps: false, + format: esrijsonFormat, + url: function(extent, resolution, proj) { + const baseUrl = this.options.url + '/' + this.options.layer + '/query/'; + const geometry = encodeURIComponent( + '{"xmin":' + + extent[0] + + ',"ymin":' + + extent[1] + + ',"xmax":' + + extent[2] + + ',"ymax":' + + extent[3] + + ',"spatialReference":{"wkid":102100}}' + ); + const params = [ + 'f=json', + `geometry=${geometry}`, + 'geometryType=esriGeometryEnvelope', + 'inSR=102100', + 'spatialRel=esriSpatialRelIntersects', + 'outFields=*', + 'returnGeometry=true', + 'outSR=102100' + ]; + if (this.options.params.timeFilter) { + let time = `time=${this.options.params.timeExtent}`; + params.push(time); + } + return `${baseUrl}?${params.join('&')}`; + }.bind(this), + strategy: olloadingstrategy.bbox + }); + } + + protected generateId() { + return uuid(); + } +} diff --git a/projects/geo/src/lib/datasource/shared/datasources/carto-datasource.interface.ts b/projects/geo/src/lib/datasource/shared/datasources/carto-datasource.interface.ts new file mode 100644 index 0000000000..f4442212da --- /dev/null +++ b/projects/geo/src/lib/datasource/shared/datasources/carto-datasource.interface.ts @@ -0,0 +1,17 @@ +import olSourceCarto from 'ol/source/CartoDB'; + +import { DataSourceOptions } from './datasource.interface'; + +export interface CartoDataSourceOptions extends DataSourceOptions { + // type?: 'carto'; + params?: any; + queryPrecision?: string; + + crossOrigin?: string; + projection?: string; + config?: any; + map?: string; + account?: string; + + ol?: olSourceCarto; +} diff --git a/projects/geo/src/lib/datasource/shared/datasources/carto-datasource.ts b/projects/geo/src/lib/datasource/shared/datasources/carto-datasource.ts new file mode 100644 index 0000000000..dce306328f --- /dev/null +++ b/projects/geo/src/lib/datasource/shared/datasources/carto-datasource.ts @@ -0,0 +1,35 @@ +import olSourceCarto from 'ol/source/CartoDB'; + +import { uuid } from '@igo2/utils'; +import { DataSource } from './datasource'; +import { DataSourceLegendOptions } from './datasource.interface'; +import { CartoDataSourceOptions } from './carto-datasource.interface'; + +export class CartoDataSource extends DataSource { + public ol: olSourceCarto; + public options: CartoDataSourceOptions; + + get params(): any { + return this.options.params as any; + } + + get queryTitle(): string { + return (this.options as any).queryTitle + ? (this.options as any).queryTitle + : 'title'; + } + + get queryHtmlTarget(): string { + return (this.options as any).queryHtmlTarget + ? (this.options as any).queryHtmlTarget + : 'newtab'; + } + + protected createOlSource(): olSourceCarto { + return new olSourceCarto(this.options); + } + + protected generateId() { + return uuid(); + } +} diff --git a/projects/geo/src/lib/datasource/shared/datasources/datasource.interface.ts b/projects/geo/src/lib/datasource/shared/datasources/datasource.interface.ts index 884fb05bfb..9cbb3f149b 100644 --- a/projects/geo/src/lib/datasource/shared/datasources/datasource.interface.ts +++ b/projects/geo/src/lib/datasource/shared/datasources/datasource.interface.ts @@ -3,7 +3,16 @@ import olSource from 'ol/source/Source'; import { DataSource } from './datasource'; export interface DataSourceOptions { - type?: 'wms' | 'wfs' | 'vector' | 'wmts' | 'xyz' | 'osm'; + type?: + | 'wms' + | 'wfs' + | 'vector' + | 'wmts' + | 'xyz' + | 'osm' + | 'carto' + | 'arcgisrest' + | 'tilearcgisrest'; legend?: DataSourceLegendOptions; // title: string; // alias?: string; diff --git a/projects/geo/src/lib/datasource/shared/datasources/index.ts b/projects/geo/src/lib/datasource/shared/datasources/index.ts index 88fba4bcbd..39cecd1518 100644 --- a/projects/geo/src/lib/datasource/shared/datasources/index.ts +++ b/projects/geo/src/lib/datasource/shared/datasources/index.ts @@ -12,5 +12,11 @@ export * from './wms-datasource'; export * from './wms-datasource.interface'; export * from './wmts-datasource'; export * from './wmts-datasource.interface'; +export * from './carto-datasource'; +export * from './carto-datasource.interface'; +export * from './arcgisrest-datasource'; +export * from './arcgisrest-datasource.interface'; +export * from './tilearcgisrest-datasource'; +export * from './tilearcgisrest-datasource.interface'; export * from './any-datasource'; export * from './any-datasource.interface'; diff --git a/projects/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.interface.ts b/projects/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.interface.ts new file mode 100644 index 0000000000..8484036412 --- /dev/null +++ b/projects/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.interface.ts @@ -0,0 +1,19 @@ +import olSourceTileArcGISRest from 'ol/source/TileArcGISRest'; +import olAttribution from 'ol/control/Attribution'; + +import { DataSourceOptions } from './datasource.interface'; + +export interface TileArcGISRestDataSourceOptions extends DataSourceOptions { + //type?: 'tilearcgisrest'; + queryPrecision?: number; + layer: string; + legendInfo?: any; + + params?: any; + attributions?: olAttribution; + projection?: string; + url?: string; + urls?: string[]; + + ol?: olSourceTileArcGISRest; +} diff --git a/projects/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts b/projects/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts new file mode 100644 index 0000000000..c8ecccf758 --- /dev/null +++ b/projects/geo/src/lib/datasource/shared/datasources/tilearcgisrest-datasource.ts @@ -0,0 +1,62 @@ +import olSourceTileArcGISRest from 'ol/source/TileArcGISRest'; +import * as olextent from 'ol/extent'; + +import { uuid } from '@igo2/utils'; +import { DataSource } from './datasource'; +import { DataSourceLegendOptions } from './datasource.interface'; +import { TileArcGISRestDataSourceOptions } from './tilearcgisrest-datasource.interface'; + +import { QueryFormat } from '../../../query/shared/query.enum'; + +export class TileArcGISRestDataSource extends DataSource { + public ol: olSourceTileArcGISRest; + + constructor(public options: TileArcGISRestDataSourceOptions) { + super(options); + } + + get params(): any { + return this.options.params as any; + } + + get queryTitle(): string { + return (this.options as any).queryTitle + ? (this.options as any).queryTitle + : 'title'; + } + + get queryHtmlTarget(): string { + return (this.options as any).queryHtmlTarget + ? (this.options as any).queryHtmlTarget + : 'newtab'; + } + + protected createOlSource(): olSourceTileArcGISRest { + return new olSourceTileArcGISRest(this.options); + } + + protected generateId() { + return uuid(); + } + + getLegend(): DataSourceLegendOptions[] { + const id = parseInt(this.options.layer); + const lyr = this.options.legendInfo.layers[id]; + let htmlString = ''; + + for (let i = 0; i < lyr.legend.length; i++) { + const src = `${this.options.url}/${lyr.layerId}/images/${ + lyr.legend[i].url + }`; + const label = lyr.legend[i].label.replace('', 'Null'); + htmlString += + "'; + } + htmlString += '
' + lyr.layerName + '
" + + label + + '
'; + return [{ html: htmlString }]; + } +} diff --git a/projects/geo/src/lib/datasource/utils/esri-style-generator.ts b/projects/geo/src/lib/datasource/utils/esri-style-generator.ts new file mode 100644 index 0000000000..de15c81678 --- /dev/null +++ b/projects/geo/src/lib/datasource/utils/esri-style-generator.ts @@ -0,0 +1,374 @@ +import * as olstyle from 'ol/style'; +import * as olproj from 'ol/proj'; + +export class EsriStyleGenerator { + public _converters: any; + public _renderers: any; + + constructor() { + this._converters = {}; + this._converters.esriPMS = EsriStyleGenerator._convertEsriPMS; + this._converters.esriSFS = EsriStyleGenerator._convertEsriSFS; + this._converters.esriSLS = EsriStyleGenerator._convertEsriSLS; + this._converters.esriSMS = EsriStyleGenerator._convertEsriSMS; + this._converters.esriTS = EsriStyleGenerator._convertEsriTS; + this._renderers = {}; + this._renderers.uniqueValue = this._renderUniqueValue; + this._renderers.simple = this._renderSimple; + this._renderers.classBreaks = this._renderClassBreaks; + } + static _convertPointToPixel(point) { + return point / 0.75; + } + static _transformColor(color): [number, number, number, number] { + // alpha channel is different, runs from 0-255 but in ol3 from 0-1 + return [color[0], color[1], color[2], color[3] / 255]; + } + + static _getResolutionForScale(scale, units) { + var dpi = 25.4 / 0.28; + var mpu = olproj.METERS_PER_UNIT[units]; + var inchesPerMeter = 39.37; + return parseFloat(scale) / (mpu * inchesPerMeter * dpi); + } + + _convertLabelingInfo(labelingInfo, mapUnits) { + var styles = []; + for (var i = 0, ii = labelingInfo.length; i < ii; ++i) { + var labelExpression = labelingInfo[i].labelExpression; + // only limited support for label expressions + var field = labelExpression.substr( + labelExpression.indexOf('[') + 1, + labelExpression.indexOf(']') - 1 + ); + var symbol = labelingInfo[i].symbol; + var maxScale = labelingInfo[i].maxScale; + var minScale = labelingInfo[i].minScale; + var minResolution = null; + if (maxScale !== 0) { + minResolution = EsriStyleGenerator._getResolutionForScale( + maxScale, + mapUnits + ); + } + var maxResolution = null; + if (minScale !== 0) { + maxResolution = EsriStyleGenerator._getResolutionForScale( + minScale, + mapUnits + ); + } + var style = this._converters[symbol.type].call(this, symbol); + styles.push( + (function() { + return function(feature, resolution) { + var visible = true; + if (this.minResolution !== null && this.maxResolution !== null) { + visible = + resolution < this.maxResolution && + resolution >= this.minResolution; + } else if (this.minResolution !== null) { + visible = resolution >= this.minResolution; + } else if (this.maxResolution !== null) { + visible = resolution < this.maxResolution; + } + if (visible) { + var value = feature.get(this.field); + this.style.getText().setText(value); + return [this.style]; + } + }; + })().bind({ + minResolution: minResolution, + maxResolution: maxResolution, + field: field, + style: style + }) + ); + } + return styles; + } + /* convert an Esri Text Symbol */ + static _convertEsriTS(symbol) { + var rotation = EsriStyleGenerator._transformAngle(symbol.angle); + var text = symbol.text !== undefined ? symbol.text : undefined; + return new olstyle.Style({ + text: new olstyle.Text({ + fill: new olstyle.Fill({ + color: EsriStyleGenerator._transformColor(symbol.color) + }), + font: + symbol.font.style + + ' ' + + symbol.font.weight + + ' ' + + symbol.font.size + + ' px ' + + symbol.font.family, + textBaseline: symbol.verticalAlignment, + textAlign: symbol.horizontalAlignment, + offsetX: EsriStyleGenerator._convertPointToPixel(symbol.xoffset), + offsetY: EsriStyleGenerator._convertPointToPixel(symbol.yoffset), + rotation: rotation, + text: text + }) + }); + } + /* convert an Esri Picture Marker Symbol */ + static _convertEsriPMS(symbol) { + var src = 'data:' + symbol.contentType + ';base64, ' + symbol.imageData; + var rotation = EsriStyleGenerator._transformAngle(symbol.angle); + + return new olstyle.Style({ + image: new olstyle.Icon({ + src: src, + rotation: rotation + }) + }); + } + /* convert an Esri Simple Fill Symbol */ + static _convertEsriSFS(symbol) { + // there is no support in openlayers currently for fill patterns, so style is not interpreted + var fill = new olstyle.Fill({ + color: EsriStyleGenerator._transformColor(symbol.color) + }); + var stroke = symbol.outline + ? EsriStyleGenerator._convertOutline(symbol.outline) + : undefined; + return new olstyle.Style({ + fill: fill, + stroke: stroke + }); + } + static _convertOutline(outline) { + var lineDash; + var color = EsriStyleGenerator._transformColor(outline.color); + if (outline.style === 'esriSLSDash') { + lineDash = [5]; + } else if (outline.style === 'esriSLSDashDot') { + lineDash = [5, 5, 1, 2]; + } else if (outline.style === 'esriSLSDashDotDot') { + lineDash = [5, 5, 1, 2, 1, 2]; + } else if (outline.style === 'esriSLSDot') { + lineDash = [1, 2]; + } else if (outline.style === 'esriSLSNull') { + // line not visible, make color fully transparent + color[3] = 0; + } + return new olstyle.Stroke({ + color: color, + lineDash: lineDash, + width: EsriStyleGenerator._convertPointToPixel(outline.width) + }); + } + /* convert an Esri Simple Line Symbol */ + static _convertEsriSLS(symbol) { + return new olstyle.Style({ + stroke: EsriStyleGenerator._convertOutline(symbol) + }); + } + static _transformAngle(angle) { + if (angle === 0 || angle === undefined) { + return undefined; + } + var normalRad = (angle * Math.PI) / 180; + var ol3Rad = -normalRad + Math.PI / 2; + if (ol3Rad < 0) { + return 2 * Math.PI + ol3Rad; + } else { + return ol3Rad; + } + } + /* convert an Esri Simple Marker Symbol */ + static _convertEsriSMS(symbol) { + var fill = new olstyle.Fill({ + color: EsriStyleGenerator._transformColor(symbol.color) + }); + var stroke = symbol.outline + ? EsriStyleGenerator._convertOutline(symbol.outline) + : undefined; + var radius = EsriStyleGenerator._convertPointToPixel(symbol.size) / 2; + var rotation = EsriStyleGenerator._transformAngle(symbol.angle); + if (symbol.style === 'esriSMSCircle') { + return new olstyle.Style({ + image: new olstyle.Circle({ + radius: radius, + fill: fill, + stroke: stroke + }) + }); + } else if (symbol.style === 'esriSMSCross') { + return new olstyle.Style({ + image: new olstyle.RegularShape({ + fill: fill, + stroke: stroke, + points: 4, + radius: radius, + radius2: 0, + angle: 0, + rotation: rotation + }) + }); + } else if (symbol.style === 'esriSMSDiamond') { + return new olstyle.Style({ + image: new olstyle.RegularShape({ + fill: fill, + stroke: stroke, + points: 4, + radius: radius, + rotation: rotation + }) + }); + } else if (symbol.style === 'esriSMSSquare') { + return new olstyle.Style({ + image: new olstyle.RegularShape({ + fill: fill, + stroke: stroke, + points: 4, + radius: radius, + angle: Math.PI / 4, + rotation: rotation + }) + }); + } else if (symbol.style === 'esriSMSX') { + return new olstyle.Style({ + image: new olstyle.RegularShape({ + fill: fill, + stroke: stroke, + points: 4, + radius: radius, + radius2: 0, + angle: Math.PI / 4, + rotation: rotation + }) + }); + } else if (symbol.style === 'esriSMSTriangle') { + return new olstyle.Style({ + image: new olstyle.RegularShape({ + fill: fill, + stroke: stroke, + points: 3, + radius: radius, + angle: 0, + rotation: rotation + }) + }); + } + } + _renderSimple(renderer) { + var style = this._converters[renderer.symbol.type].call( + this, + renderer.symbol + ); + return (function() { + return function() { + return [style]; + }; + })(); + } + _renderClassBreaks(renderer) { + var defaultSymbol = renderer.defaultSymbol; + var defaultStyle = this._converters[defaultSymbol.type].call( + this, + defaultSymbol + ); + var field = renderer.field; + var classes = []; + for (var i = 0, ii = renderer.classBreakInfos.length; i < ii; ++i) { + var classBreakInfo = renderer.classBreakInfos[i]; + var min; + if ( + classBreakInfo.classMinValue === null || + classBreakInfo.classMinValue === undefined + ) { + if (i === 0) { + min = renderer.minValue; + } else { + min = renderer.classBreakInfos[i - 1].classMaxValue; + } + } else { + min = classBreakInfo.classMinValue; + } + var max = classBreakInfo.classMaxValue; + var symbol = classBreakInfo.symbol; + var style = this._converters[symbol.type].call(this, symbol); + classes.push({ min: min, max: max, style: style }); + } + return (function() { + return function(feature) { + var value = feature.get(field); + for (i = 0, ii = classes.length; i < ii; ++i) { + var condition; + if (i === 0) { + condition = value >= classes[i].min && value <= classes[i].max; + } else { + condition = value > classes[i].min && value <= classes[i].max; + } + if (condition) { + return [classes[i].style]; + } + } + return [defaultStyle]; + }; + })(); + } + _renderUniqueValue(renderer) { + var defaultSymbol = renderer.defaultSymbol; + var defaultStyle = []; + if (defaultSymbol) { + defaultStyle = [ + this._converters[defaultSymbol.type].call(this, defaultSymbol) + ]; + } + var field = renderer.field1; + var infos = renderer.uniqueValueInfos; + var me = this; + return (function() { + var hash = {}; + for (var i = 0, ii = infos.length; i < ii; ++i) { + var info = infos[i], + symbol = info.symbol; + hash[info.value] = [me._converters[symbol.type].call(me, symbol)]; + } + + return function(feature) { + var style = hash[feature.get(field)]; + return style ? style : defaultStyle; + }; + })(); + } + generateStyle(layerInfo, mapUnits) { + var drawingInfo = layerInfo.drawingInfo; + var styleFunctions = []; + var drawingInfoStyle = this._renderers[drawingInfo.renderer.type].call( + this, + drawingInfo.renderer + ); + if (drawingInfoStyle !== undefined) { + styleFunctions.push(drawingInfoStyle); + } + if (layerInfo.labelingInfo) { + var labelingInfoStyleFunctions = this._convertLabelingInfo( + layerInfo.labelingInfo, + mapUnits + ); + styleFunctions = styleFunctions.concat(labelingInfoStyleFunctions); + } + if (styleFunctions.length === 1) { + return styleFunctions[0]; + } else { + return (function() { + return function(feature, resolution) { + var styles = []; + for (var i = 0, ii = styleFunctions.length; i < ii; ++i) { + var result = styleFunctions[i].call(null, feature, resolution); + if (result) { + styles = styles.concat(result); + } + } + return styles; + }; + })(); + } + } +} diff --git a/projects/geo/src/lib/filter/shared/time-filter.service.ts b/projects/geo/src/lib/filter/shared/time-filter.service.ts index 68b3ca070b..a97887826d 100644 --- a/projects/geo/src/lib/filter/shared/time-filter.service.ts +++ b/projects/geo/src/lib/filter/shared/time-filter.service.ts @@ -2,12 +2,16 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject } from 'rxjs'; import { WMSDataSource } from '../../datasource/shared/datasources/wms-datasource'; +import { TileArcGISRestDataSource } from '../../datasource/shared/datasources/tilearcgisrest-datasource'; @Injectable() export class TimeFilterService { constructor() {} - filterByDate(datasource: WMSDataSource, date: Date | [Date, Date]) { + filterByDate( + datasource: WMSDataSource | TileArcGISRestDataSource, + date: Date | [Date, Date] + ) { let time; let newdateform; let newdateform_start; @@ -24,7 +28,11 @@ export class TimeFilterService { dates.push(date[1]); } if (dates.length === 2 && newdateform_start !== newdateform_end) { - time = newdateform_start + '/' + newdateform_end; + if (datasource instanceof TileArcGISRestDataSource) { + time = newdateform_start + ',' + newdateform_end; + } else { + time = newdateform_start + '/' + newdateform_end; + } } if (newdateform_start === newdateform_end) { time = newdateform_start; @@ -38,7 +46,10 @@ export class TimeFilterService { datasource.ol.updateParams(params); } - filterByYear(datasource: WMSDataSource, year: string | [string, string]) { + filterByYear( + datasource: WMSDataSource | TileArcGISRestDataSource, + year: string | [string, string] + ) { let time; let newdateform_start; let newdateform_end; @@ -54,7 +65,11 @@ export class TimeFilterService { years.push(year[1]); } if (years.length === 2 && newdateform_start !== newdateform_end) { - time = newdateform_start + '/' + newdateform_end; + if (datasource instanceof TileArcGISRestDataSource) { + time = newdateform_start + ',' + newdateform_end; + } else { + time = newdateform_start + '/' + newdateform_end; + } } if (newdateform_start === newdateform_end) { time = newdateform_start; diff --git a/projects/geo/src/lib/layer/shared/layer.service.ts b/projects/geo/src/lib/layer/shared/layer.service.ts index f0c37542de..bcf25f5fa5 100644 --- a/projects/geo/src/lib/layer/shared/layer.service.ts +++ b/projects/geo/src/lib/layer/shared/layer.service.ts @@ -10,7 +10,10 @@ import { XYZDataSource, WFSDataSource, WMTSDataSource, - WMSDataSource + WMSDataSource, + CartoDataSource, + ArcGISRestDataSource, + TileArcGISRestDataSource } from '../../datasource'; import { DataSourceService } from '../../datasource/shared/datasource.service'; @@ -54,10 +57,13 @@ export class LayerService { case OSMDataSource: case WMTSDataSource: case XYZDataSource: + case CartoDataSource: + case TileArcGISRestDataSource: layer = this.createTileLayer(layerOptions as TileLayerOptions); break; case FeatureDataSource: case WFSDataSource: + case ArcGISRestDataSource: layer = this.createVectorLayer(layerOptions as VectorLayerOptions); break; case WMSDataSource: @@ -103,6 +109,11 @@ export class LayerService { style = this.styleService.createStyle(layerOptions.style); } + if (layerOptions.source instanceof ArcGISRestDataSource) { + const source = layerOptions.source as ArcGISRestDataSource; + style = source.options.params.style; + } + const layerOptionsOl = Object.assign({}, layerOptions, { style: style }); diff --git a/projects/geo/src/lib/layer/shared/layers/tile-layer.interface.ts b/projects/geo/src/lib/layer/shared/layers/tile-layer.interface.ts index 67f0cd949c..677d3e65d4 100644 --- a/projects/geo/src/lib/layer/shared/layers/tile-layer.interface.ts +++ b/projects/geo/src/lib/layer/shared/layers/tile-layer.interface.ts @@ -5,16 +5,27 @@ import { LayerOptions } from './layer.interface'; import { OSMDataSource } from '../../../datasource/shared/datasources/osm-datasource'; import { WMTSDataSource } from '../../../datasource/shared/datasources/wmts-datasource'; import { XYZDataSource } from '../../../datasource/shared/datasources/xyz-datasource'; +import { CartoDataSource } from '../../../datasource/shared/datasources/carto-datasource'; +import { TileArcGISRestDataSource } from '../../../datasource/shared/datasources/tilearcgisrest-datasource'; import { OSMDataSourceOptions } from '../../../datasource/shared/datasources/osm-datasource.interface'; import { WMTSDataSourceOptions } from '../../../datasource/shared/datasources/wmts-datasource.interface'; import { XYZDataSourceOptions } from '../../../datasource/shared/datasources/xyz-datasource.interface'; +import { CartoDataSourceOptions } from '../../../datasource/shared/datasources/carto-datasource.interface'; +import { TileArcGISRestDataSourceOptions } from '../../../datasource/shared/datasources/tilearcgisrest-datasource.interface'; export interface TileLayerOptions extends LayerOptions { - source?: OSMDataSource | WMTSDataSource | XYZDataSource; + source?: + | OSMDataSource + | WMTSDataSource + | XYZDataSource + | CartoDataSource + | TileArcGISRestDataSource; sourceOptions?: | OSMDataSourceOptions | WMTSDataSourceOptions - | XYZDataSourceOptions; + | XYZDataSourceOptions + | CartoDataSourceOptions + | TileArcGISRestDataSourceOptions; ol?: olLayerTile; } diff --git a/projects/geo/src/lib/layer/shared/layers/tile-layer.ts b/projects/geo/src/lib/layer/shared/layers/tile-layer.ts index 24e7748815..66f08f571e 100644 --- a/projects/geo/src/lib/layer/shared/layers/tile-layer.ts +++ b/projects/geo/src/lib/layer/shared/layers/tile-layer.ts @@ -7,12 +7,19 @@ import { IgoMap } from '../../../map'; import { OSMDataSource } from '../../../datasource/shared/datasources/osm-datasource'; import { WMTSDataSource } from '../../../datasource/shared/datasources/wmts-datasource'; import { XYZDataSource } from '../../../datasource/shared/datasources/xyz-datasource'; +import { CartoDataSource } from '../../../datasource/shared/datasources/carto-datasource'; +import { TileArcGISRestDataSource } from '../../../datasource/shared/datasources/tilearcgisrest-datasource'; import { Layer } from './layer'; import { TileLayerOptions } from './tile-layer.interface'; export class TileLayer extends Layer { - public dataSource: OSMDataSource | WMTSDataSource | XYZDataSource; + public dataSource: + | OSMDataSource + | WMTSDataSource + | XYZDataSource + | CartoDataSource + | TileArcGISRestDataSource; public options: TileLayerOptions; public ol: olLayerTile; diff --git a/projects/geo/src/lib/layer/shared/layers/vector-layer.interface.ts b/projects/geo/src/lib/layer/shared/layers/vector-layer.interface.ts index 462da4250f..a0af15b8de 100644 --- a/projects/geo/src/lib/layer/shared/layers/vector-layer.interface.ts +++ b/projects/geo/src/lib/layer/shared/layers/vector-layer.interface.ts @@ -5,13 +5,18 @@ import { LayerOptions } from './layer.interface'; import { FeatureDataSource } from '../../../datasource/shared/datasources/feature-datasource'; import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; +import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arcgisrest-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'; export interface VectorLayerOptions extends LayerOptions { - source?: FeatureDataSource | WFSDataSource; - sourceOptions?: FeatureDataSourceOptions | WFSDataSourceOptions; + source?: FeatureDataSource | WFSDataSource | ArcGISRestDataSource; + sourceOptions?: + | FeatureDataSourceOptions + | WFSDataSourceOptions + | ArcGISRestDataSourceOptions; style?: { [key: string]: any } | olStyle | olStyle[]; ol?: olLayerVector; } diff --git a/projects/geo/src/lib/layer/shared/layers/vector-layer.ts b/projects/geo/src/lib/layer/shared/layers/vector-layer.ts index 52053222d9..b13f98e881 100644 --- a/projects/geo/src/lib/layer/shared/layers/vector-layer.ts +++ b/projects/geo/src/lib/layer/shared/layers/vector-layer.ts @@ -3,12 +3,13 @@ import olSourceVector from 'ol/source/Vector'; import { FeatureDataSource } from '../../../datasource/shared/datasources/feature-datasource'; import { WFSDataSource } from '../../../datasource/shared/datasources/wfs-datasource'; +import { ArcGISRestDataSource } from '../../../datasource/shared/datasources/arcgisrest-datasource'; import { Layer } from './layer'; import { VectorLayerOptions } from './vector-layer.interface'; export class VectorLayer extends Layer { - public dataSource: FeatureDataSource | WFSDataSource; + public dataSource: FeatureDataSource | WFSDataSource | ArcGISRestDataSource; public options: VectorLayerOptions; public ol: olLayerVector; diff --git a/projects/geo/src/lib/query/shared/query.enum.ts b/projects/geo/src/lib/query/shared/query.enum.ts index 27fbc21ffd..440d00bf9e 100644 --- a/projects/geo/src/lib/query/shared/query.enum.ts +++ b/projects/geo/src/lib/query/shared/query.enum.ts @@ -3,6 +3,7 @@ export enum QueryFormat { GML3 = 'gml3', JSON = 'json', GEOJSON = 'geojson', + ESRIJSON = 'esrijson', TEXT = 'text', HTML = 'html' } diff --git a/projects/geo/src/lib/query/shared/query.service.ts b/projects/geo/src/lib/query/shared/query.service.ts index f7813a899e..5b1beefc3b 100644 --- a/projects/geo/src/lib/query/shared/query.service.ts +++ b/projects/geo/src/lib/query/shared/query.service.ts @@ -4,8 +4,10 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import * as olformat from 'ol/format'; +import * as olextent from 'ol/extent'; import olFormatGML2 from 'ol/format/GML2'; import olFormatGML3 from 'ol/format/GML3'; +import olFormatEsriJSON from 'ol/format/EsriJSON'; import olFeature from 'ol/Feature'; import { uuid } from '@igo2/utils'; @@ -18,7 +20,11 @@ import { } from '../../feature/shared/feature.enum'; import { DataSource } from '../../datasource/shared/datasources/datasource'; import { Layer } from '../../layer/shared/layers/layer'; -import { WMSDataSource } from '../../datasource'; +import { + WMSDataSource, + CartoDataSource, + TileArcGISRestDataSource +} from '../../datasource'; import { QueryFormat } from './query.enum'; import { QueryOptions, QueryableDataSource } from './query.interface'; @@ -64,6 +70,9 @@ export class QueryService { case QueryFormat.GEOJSON: features = this.extractGeoJSONData(res); break; + case QueryFormat.ESRIJSON: + features = this.extractEsriJSONData(res, layer.zIndex); + break; case QueryFormat.TEXT: features = this.extractTextData(res); break; @@ -89,7 +98,10 @@ export class QueryService { sourceType: SourceFeatureType.Query, order: 1000 - layer.zIndex, title: title ? title : `${layer.title} (${index + 1})`, - projection: options.projection + projection: + queryDataSource.options.type === 'carto' + ? 'EPSG:4326' + : options.projection }); }); } @@ -124,6 +136,13 @@ export class QueryService { return features; } + private extractEsriJSONData(res, zIndex) { + const parser = new olFormatEsriJSON(); + const features = parser.readFeatures(res); + + return features.map(feature => this.featureToResult(feature, zIndex)); + } + private extractTextData(res) { // TODO return []; @@ -300,6 +319,67 @@ export class QueryService { } ); break; + case CartoDataSource: + const cartoDatasource = datasource as CartoDataSource; + const baseUrl = + 'https://' + + cartoDatasource.options.account + + '.carto.com/api/v2/sql?'; + const format = 'format=GeoJSON'; + const sql = + '&q=' + cartoDatasource.options.config.layers[0].options.sql; + const clause = + ' WHERE ST_Intersects(the_geom_webmercator,ST_BUFFER(ST_SetSRID(ST_POINT('; + const metres = cartoDatasource.options.queryPrecision + ? cartoDatasource.options.queryPrecision + : '1000'; + const coordinates = + options.coordinates[0] + + ',' + + options.coordinates[1] + + '),3857),' + + metres + + '))'; + + url = `${baseUrl}${format}${sql}${clause}${coordinates}`; + break; + case TileArcGISRestDataSource: + const tileArcGISRestDatasource = datasource as TileArcGISRestDataSource; + let extent = olextent.boundingExtent([options.coordinates]); + if (tileArcGISRestDatasource.options.queryPrecision) { + extent = olextent.buffer( + extent, + tileArcGISRestDatasource.options.queryPrecision + ); + } + const serviceUrl = + tileArcGISRestDatasource.options.url + + '/' + + tileArcGISRestDatasource.options.layer + + '/query/'; + const geometry = encodeURIComponent( + '{"xmin":' + + extent[0] + + ',"ymin":' + + extent[1] + + ',"xmax":' + + extent[2] + + ',"ymax":' + + extent[3] + + ',"spatialReference":{"wkid":102100}}' + ); + const params = [ + 'f=json', + `geometry=${geometry}`, + 'geometryType=esriGeometryEnvelope', + 'inSR=102100', + 'spatialRel=esriSpatialRelIntersects', + 'outFields=*', + 'returnGeometry=true', + 'outSR=102100' + ]; + url = `${serviceUrl}?${params.join('&')}`; + break; default: break; }