forked from geosolutions-it/MapStore2
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
geosolutions-it#10040 ArcGIS Interoperability - ArcGIS MapServer Cata…
…log and Layer support (geosolutions-it#10330) --------- Co-authored-by: Igor Dimov <[email protected]>
- Loading branch information
1 parent
c4464fc
commit 9baf447
Showing
36 changed files
with
1,614 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
/* | ||
* Copyright 2024, GeoSolutions Sas. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
|
||
import axios from '../libs/ajax'; | ||
import { reprojectBbox } from '../utils/CoordinatesUtils'; | ||
import trimEnd from 'lodash/trimEnd'; | ||
|
||
let _cache = {}; | ||
|
||
const extentToBoundingBox = (extent) => { | ||
const wkid = extent?.spatialReference?.wkt | ||
? '4326' | ||
: extent?.spatialReference?.latestWkid || extent?.spatialReference?.wkid; | ||
const projectedExtent = extent?.spatialReference?.wkt | ||
? reprojectBbox([extent?.xmin, extent?.ymin, extent?.xmax, extent?.ymax], extent.spatialReference.wkt, 'EPSG:4326') | ||
: extent | ||
? [extent?.xmin, extent?.ymin, extent?.xmax, extent?.ymax] | ||
: null; | ||
|
||
if (projectedExtent) { | ||
return { | ||
bounds: { | ||
minx: projectedExtent[0], | ||
miny: projectedExtent[1], | ||
maxx: projectedExtent[2], | ||
maxy: projectedExtent[3] | ||
}, | ||
crs: `EPSG:${wkid}` | ||
}; | ||
} | ||
return null; | ||
}; | ||
|
||
/** | ||
* Retrieve layer metadata. | ||
* | ||
* @param {string} layerUrl - url of the rest service | ||
* @param {string} layerName - id of the layer | ||
* @returns layer metadata | ||
*/ | ||
export const getLayerMetadata = (layerUrl, layerName) => { | ||
return axios.get(`${trimEnd(layerUrl, '/')}/${layerName}`, { params: { f: 'json' }}) | ||
.then(({ data }) => { | ||
const bbox = extentToBoundingBox(data?.extent); | ||
return { | ||
...(bbox && { bbox }), | ||
data | ||
}; | ||
}); | ||
}; | ||
export const searchAndPaginate = (records, params) => { | ||
const { startPosition, maxRecords, text } = params; | ||
const filteredLayers = records?.filter(layer => !text || layer?.name.toLowerCase().indexOf(text.toLowerCase()) !== -1); | ||
return { | ||
numberOfRecordsMatched: filteredLayers.length, | ||
numberOfRecordsReturned: Math.min(maxRecords, filteredLayers.length), | ||
records: filteredLayers.filter((layer, index) => index >= startPosition - 1 && index < startPosition - 1 + maxRecords) | ||
}; | ||
}; | ||
const getData = (url, params = {}) => { | ||
const request = _cache[url] | ||
? () => Promise.resolve(_cache[url]) | ||
: () => axios.get(url, { | ||
params: { | ||
f: 'json' | ||
} | ||
}).then(({ data }) => { | ||
_cache[url] = data; | ||
return data; | ||
}); | ||
return request() | ||
.then((data) => { | ||
const { layers } = data || {}; | ||
// Map is similar to WMS GetMap capability for MapServer | ||
const mapExportSupported = (data?.capabilities || '').includes('Map'); | ||
const commonProperties = { | ||
url, | ||
version: data?.currentVersion, | ||
format: (data.supportedImageFormatTypes || '') | ||
.split(',') | ||
.filter(format => /PNG|JPG|GIF/.test(format))[0] || 'PNG32' | ||
}; | ||
const bbox = extentToBoundingBox(data?.fullExtent); | ||
const records = [ | ||
...((mapExportSupported) ? [ | ||
{ | ||
name: data?.documentInfo?.Title || data.name || params?.info?.options?.service?.title || data.mapName, | ||
description: data.description || data.serviceDescription, | ||
bbox, | ||
queryable: (data?.capabilities || '').includes('Data'), | ||
layers, | ||
...commonProperties | ||
} | ||
] : []), | ||
...(mapExportSupported && layers ? layers : []).map((layer) => { | ||
return { | ||
...layer, | ||
...commonProperties, | ||
queryable: (data?.capabilities || '').includes('Data') | ||
}; | ||
}) | ||
]; | ||
return searchAndPaginate(records, params); | ||
}); | ||
}; | ||
/** | ||
* Retrieve arcgis service capabilities. | ||
* @param {string} url - url of the rest service | ||
* @param {number} startPosition - pagination start position | ||
* @param {number} maxRecords - maximum number of records | ||
* @param {string} text - search text | ||
* @param {object} info | ||
* @returns {object} | ||
* - numberOfRecordsMatched | ||
* - numberOfRecordsReturned | ||
* - {array} records - records list | ||
*/ | ||
export const getCapabilities = (url, startPosition, maxRecords, text, info) => { | ||
return getData(url, { startPosition, maxRecords, text, info }) | ||
.then(({ numberOfRecordsMatched, numberOfRecordsReturned, records }) => { | ||
return { numberOfRecordsMatched, numberOfRecordsReturned, records }; | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* Copyright 2024, GeoSolutions Sas. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
import { getCapabilities, getLayerMetadata } from '../ArcGIS'; | ||
import expect from 'expect'; | ||
|
||
describe('Test ArcGIS API', () => { | ||
const _url = 'base/web/client/test-resources/arcgis/arcgis-test-data.json'; | ||
|
||
it('should extract capabilities from arcgis service data', (done) => { | ||
getCapabilities(_url, 1, 30, '').then((data) => { | ||
const { numberOfRecordsMatched, numberOfRecordsReturned, records } = data; | ||
const { type, url, name, version, defaultVisibility } = records[1]; | ||
try { | ||
expect(numberOfRecordsMatched).toBeTruthy(); | ||
expect(numberOfRecordsMatched).toBe(25); | ||
|
||
expect(numberOfRecordsReturned).toBeTruthy(); | ||
expect(numberOfRecordsReturned).toBe(25); | ||
|
||
expect(type).toBeTruthy(); | ||
expect(type).toBe('Group Layer'); | ||
|
||
expect(url).toBeTruthy(); | ||
expect(url).toBe(_url); | ||
|
||
expect(name).toBeTruthy(); | ||
expect(name).toBe('Active Projects'); | ||
|
||
expect(version).toBeTruthy(); | ||
expect(version).toBe(10.81); | ||
|
||
expect(defaultVisibility).toEqual(true); | ||
} catch (e) { | ||
done(e); | ||
} | ||
done(); | ||
}); | ||
}); | ||
it('should search and paginate arcgis service data', (done) => { | ||
const startPosition = 1; | ||
const maxRecords = 4; | ||
const text = 'Outreach'; | ||
getCapabilities(_url, startPosition, maxRecords, text).then((data) => { | ||
const { numberOfRecordsMatched, numberOfRecordsReturned, records } = data; | ||
try { | ||
expect(numberOfRecordsMatched).toBeTruthy(); | ||
expect(numberOfRecordsMatched).toBe(4); | ||
|
||
expect(numberOfRecordsReturned).toBeTruthy(); | ||
expect(numberOfRecordsReturned).toBe(4); | ||
|
||
records?.forEach(element => { | ||
const { type, url, name, version, defaultVisibility } = element; | ||
|
||
expect(type).toBeTruthy(); | ||
expect(type).toBe('Feature Layer'); | ||
|
||
expect(url).toBeTruthy(); | ||
expect(url).toBe(_url); | ||
|
||
expect(name).toBeTruthy(); | ||
expect(String(name).includes(text)).toBeTruthy(); | ||
|
||
expect(version).toBeTruthy(); | ||
expect(version).toBe(10.81); | ||
|
||
expect(defaultVisibility).toEqual(true); | ||
}); | ||
} catch (e) { | ||
done(e); | ||
} | ||
done(); | ||
}); | ||
}); | ||
it('should retrieve arcgis layer metadata', (done) => { | ||
const layerPath = 'base/web/client/test-resources/arcgis'; | ||
const layerName = 'arcgis-layer-test-data.json'; | ||
getLayerMetadata(layerPath, layerName).then(({ data }) => { | ||
const { advancedQueryCapabilities, supportedQueryFormats, capabilities, extent, name, type } = data; | ||
try { | ||
expect(advancedQueryCapabilities).toBeTruthy(); | ||
|
||
expect(supportedQueryFormats).toBeTruthy(); | ||
expect(supportedQueryFormats).toBe('JSON, geoJSON, PBF'); | ||
|
||
expect(capabilities).toBeTruthy(); | ||
expect(capabilities).toBe('Map,Query'); | ||
|
||
expect(extent).toBeTruthy(); | ||
expect(extent.spatialReference).toBeTruthy(); | ||
expect(extent.xmax).toBeTruthy(); | ||
expect(extent.xmin).toBeTruthy(); | ||
expect(extent.ymax).toBeTruthy(); | ||
expect(extent.ymin).toBeTruthy(); | ||
|
||
expect(name).toBeTruthy(); | ||
expect(name).toBe('Active Projects'); | ||
|
||
expect(type).toBeTruthy(); | ||
expect(type).toBe('Group Layer'); | ||
} catch (e) { | ||
done(e); | ||
} | ||
done(); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
/* | ||
* Copyright 2024, GeoSolutions Sas. | ||
* All rights reserved. | ||
* | ||
* This source code is licensed under the BSD-style license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
*/ | ||
import { Observable } from 'rxjs'; | ||
import { isValidURL } from '../../utils/URLUtils'; | ||
import { preprocess as commonPreprocess } from './common'; | ||
import { getCapabilities } from '../ArcGIS'; | ||
|
||
function validateUrl(serviceUrl) { | ||
if (isValidURL(serviceUrl)) { | ||
return serviceUrl.includes('MapServer'); | ||
} | ||
return false; | ||
} | ||
|
||
const recordToLayer = (record) => { | ||
if (!record) { | ||
return null; | ||
} | ||
return { | ||
type: 'arcgis', | ||
url: record.url, | ||
title: record.title, | ||
format: record.format, | ||
queryable: record.queryable, | ||
visibility: true, | ||
...(record.name !== undefined && { | ||
name: `${record.name}` | ||
}), | ||
...(record.bbox && { | ||
bbox: record.bbox | ||
}), | ||
...(record.layers && { | ||
options: { | ||
layers: record.layers | ||
} | ||
}) | ||
}; | ||
}; | ||
|
||
const getRecords = (url, startPosition, maxRecords, text, info) => { | ||
return getCapabilities(url, startPosition, maxRecords, text, info); | ||
}; | ||
|
||
export const preprocess = commonPreprocess; | ||
export const testService = (service) => Observable.of(service); | ||
export const textSearch = (url, startPosition, maxRecords, text, info) => getRecords(url, startPosition, maxRecords, text, info); | ||
export const getCatalogRecords = (response) => { | ||
return response?.records | ||
? response.records.map(record => { | ||
const identifier = `${record.id !== undefined ? `Layer:${record.id}` : 'Group'}:${record.name}`; | ||
return { | ||
serviceType: 'arcgis', | ||
isValid: true, | ||
description: record.description, | ||
title: record.name, | ||
identifier, | ||
url: record.url, | ||
thumbnail: record.thumbnail ?? null, | ||
references: [], | ||
name: record.id, | ||
format: record.format, | ||
layers: record.layers, | ||
queryable: record.queryable, | ||
bbox: record.bbox | ||
}; | ||
}) | ||
: null; | ||
}; | ||
export const getLayerFromRecord = (record, options, asPromise) => { | ||
const layer = recordToLayer(record, options); | ||
return asPromise ? Promise.resolve(layer) : layer; | ||
}; | ||
export const validate = (service) => { | ||
if (service.title && validateUrl(service.url)) { | ||
return Observable.of(service); | ||
} | ||
const error = new Error("catalog.config.notValidURLTemplate"); | ||
// insert valid URL; | ||
throw error; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.