Skip to content

Commit

Permalink
geosolutions-it#8055 allow to load 3dtiles tileset via viewer parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
allyoucanmap committed Mar 15, 2023
1 parent 736dee2 commit a114f7f
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 8 deletions.
28 changes: 27 additions & 1 deletion docs/developer-guide/map-query-parameters.md
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,7 @@ Requirements:
- The number of layers should match the number of sources
- The source name can be a string that must match a catalog service name present in the map or an object that defines an external catalog (see example)
Supported layer types are WMS, WMTS and WFS.
Supported layer types are WMS, WMTS, WFS and 3D Tiles.
Example:
Expand Down Expand Up @@ -371,3 +371,29 @@ Data of resulting layer can be additionally filtered by passing "CQL_FILTER" int
GET `#/viewer/config?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["layer1","layer2","workspace:externallayername"],"sources":["catalog1","catalog2",{"type":"WMS","url":"https://example.com/wms"}],"options": [{"params":{"CQL_FILTER":"NAME='value'"}}, {}, {"params":{"CQL_FILTER":"NAME='value2'"}}]}]`
Number of objects passed to the options can be different to the number of layers, in this case options will be applied to the first X layers, where X is the length of options array.
The 3D tiles service endpoint does not contain a default property for the name of the layer and it returns only a single layer. Below some ways to correctly request the layer contained inside 3D tiles service:
- Use the `url` property to get the layer
```json
{
"type": "CATALOG:ADD_LAYERS_FROM_CATALOGS",
"layers": ["https://example.com/tileset-pathname/tileset.json"],
"sources": [{ "type":"3dtiles", "url":"https://example.com/tileset-pathname/tileset.json" }]
}
```
GET: `#/viewer/config?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["https://example.com/tileset-pathname/tileset.json"],"sources":[{"type":"3dtiles","url":"https://example.com/tileset-pathname/tileset.json"}]}]`
- Use the `title` property in the source object to assign the name to the layer
```json
{
"type": "CATALOG:ADD_LAYERS_FROM_CATALOGS",
"layers": ["My Layer"],
"sources": [{ "type":"3dtiles", "url":"https://example.com/tileset-pathname/tileset.json", "title": "My Layer" }]
}
```
GET: `#/viewer/config?actions=[{"type":"CATALOG:ADD_LAYERS_FROM_CATALOGS","layers":["My Layer"],"sources":[{"type":"3dtiles","url":"https://example.com/tileset-pathname/tileset.json","title":"My Layer"}]}]`
6 changes: 5 additions & 1 deletion web/client/api/catalog/ThreeDTiles.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,11 @@ const getRecords = (url, startPosition, maxRecords, text, info) => {
type: '3dtiles',
tileset,
...properties
}].filter(({ title }) => !text || title?.toLowerCase().includes(text?.toLowerCase() || ''));
}].filter(({ title }) =>
!text
|| title?.toLowerCase().includes(text?.toLowerCase() || '')
|| url?.toLowerCase().includes(text?.toLowerCase() || '')
);
return {
numberOfRecordsMatched: records.length,
numberOfRecordsReturned: records.length,
Expand Down
249 changes: 248 additions & 1 deletion web/client/epics/__tests__/catalog-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,11 @@ import {
ADD_CATALOG_SERVICE,
addService
} from '../../actions/catalog';

import { VISUALIZATION_MODE_CHANGED } from '../../actions/maptype';
import MockAdapter from 'axios-mock-adapter';
import axios from '../../libs/ajax';
import { VisualizationModes } from '../../utils/MapTypeUtils';
let mockAxios;

describe('catalog Epics', () => {
it('getMetadataRecordById', (done) => {
Expand Down Expand Up @@ -714,4 +718,247 @@ describe('catalog Epics', () => {
}
}, state, done);
});

describe('addLayersFromCatalogsEpic 3d tiles', () => {

beforeEach(done => {
mockAxios = new MockAdapter(axios);
setTimeout(done);
});

afterEach(done => {
mockAxios.restore();
setTimeout(done);
});
it('should add layer with url pathname search', (done) => {
const NUM_ACTIONS = 2;
const tileset = {
"asset": {
"version": "1.0"
},
"properties": {
"Height": {
"minimum": 0,
"maximum": 7
}
},
"geometricError": 70,
"root": {
"refine": "ADD",
"boundingVolume": {
"region": [
-1.3197004795898053,
0.6988582109,
-1.3196595204101946,
0.6988897891,
0,
20
]
},
"geometricError": 0,
"content": {
"uri": "model.b3dm"
}
}
};
mockAxios.onGet(/tileset\.json/).reply(() => ([ 200, tileset ]));
testEpic(
addLayersFromCatalogsEpic,
NUM_ACTIONS,
addLayersMapViewerUrl(["name"], [{ url: 'https://server.org/name/tileset.json', type: '3dtiles' }]),
(actions) => {
try {
const [
visualizationModeChangedAction,
addLayerAndDescribeAction
] = actions;
expect(visualizationModeChangedAction.type).toBe(VISUALIZATION_MODE_CHANGED);
expect(visualizationModeChangedAction.visualizationMode).toBe(VisualizationModes._3D);
expect(addLayerAndDescribeAction.type).toBe(ADD_LAYER_AND_DESCRIBE);
expect(addLayerAndDescribeAction.layer).toBeTruthy();
expect(addLayerAndDescribeAction.layer.type).toBe("3dtiles");
expect(addLayerAndDescribeAction.layer.url).toBe("https://server.org/name/tileset.json");
expect(addLayerAndDescribeAction.layer.title).toBe("name");
} catch (e) {
done(e);
}
done();
}, {});
});
it('should add layer with url search', (done) => {
const NUM_ACTIONS = 2;
const tileset = {
"asset": {
"version": "1.0"
},
"properties": {
"Height": {
"minimum": 0,
"maximum": 7
}
},
"geometricError": 70,
"root": {
"refine": "ADD",
"boundingVolume": {
"region": [
-1.3197004795898053,
0.6988582109,
-1.3196595204101946,
0.6988897891,
0,
20
]
},
"geometricError": 0,
"content": {
"uri": "model.b3dm"
}
}
};
mockAxios.onGet(/tileset\.json/).reply(() => ([ 200, tileset ]));
testEpic(
addLayersFromCatalogsEpic,
NUM_ACTIONS,
addLayersMapViewerUrl(["https://server.org/name/tileset.json"], [{ url: 'https://server.org/name/tileset.json', type: '3dtiles' }]),
(actions) => {
try {
const [
visualizationModeChangedAction,
addLayerAndDescribeAction
] = actions;
expect(visualizationModeChangedAction.type).toBe(VISUALIZATION_MODE_CHANGED);
expect(visualizationModeChangedAction.visualizationMode).toBe(VisualizationModes._3D);
expect(addLayerAndDescribeAction.type).toBe(ADD_LAYER_AND_DESCRIBE);
expect(addLayerAndDescribeAction.layer).toBeTruthy();
expect(addLayerAndDescribeAction.layer.type).toBe("3dtiles");
expect(addLayerAndDescribeAction.layer.url).toBe("https://server.org/name/tileset.json");
expect(addLayerAndDescribeAction.layer.title).toBe("name");
} catch (e) {
done(e);
}
done();
}, {});
});
it('should add layer with title', (done) => {
const NUM_ACTIONS = 2;
const tileset = {
"asset": {
"version": "1.0"
},
"properties": {
"Height": {
"minimum": 0,
"maximum": 7
}
},
"geometricError": 70,
"root": {
"refine": "ADD",
"boundingVolume": {
"region": [
-1.3197004795898053,
0.6988582109,
-1.3196595204101946,
0.6988897891,
0,
20
]
},
"geometricError": 0,
"content": {
"uri": "model.b3dm"
}
}
};
mockAxios.onGet(/tileset\.json/).reply(() => ([ 200, tileset ]));
testEpic(
addLayersFromCatalogsEpic,
NUM_ACTIONS,
addLayersMapViewerUrl(["Title"], [{ url: 'https://server.org/name/tileset.json', type: '3dtiles', title: "Title" }]),
(actions) => {
try {
const [
visualizationModeChangedAction,
addLayerAndDescribeAction
] = actions;
expect(visualizationModeChangedAction.type).toBe(VISUALIZATION_MODE_CHANGED);
expect(visualizationModeChangedAction.visualizationMode).toBe(VisualizationModes._3D);
expect(addLayerAndDescribeAction.type).toBe(ADD_LAYER_AND_DESCRIBE);
expect(addLayerAndDescribeAction.layer).toBeTruthy();
expect(addLayerAndDescribeAction.layer.type).toBe("3dtiles");
expect(addLayerAndDescribeAction.layer.url).toBe("https://server.org/name/tileset.json");
expect(addLayerAndDescribeAction.layer.title).toBe("Title");
} catch (e) {
done(e);
}
done();
}, {});
});
it('should add layer with catalog id', (done) => {
const NUM_ACTIONS = 2;
const tileset = {
"asset": {
"version": "1.0"
},
"properties": {
"Height": {
"minimum": 0,
"maximum": 7
}
},
"geometricError": 70,
"root": {
"refine": "ADD",
"boundingVolume": {
"region": [
-1.3197004795898053,
0.6988582109,
-1.3196595204101946,
0.6988897891,
0,
20
]
},
"geometricError": 0,
"content": {
"uri": "model.b3dm"
}
}
};
mockAxios.onGet(/tileset\.json/).reply(() => ([ 200, tileset ]));
testEpic(
addLayersFromCatalogsEpic,
NUM_ACTIONS,
addLayersMapViewerUrl(["name"], ["3dTilesCatalog"]),
(actions) => {
try {
const [
visualizationModeChangedAction,
addLayerAndDescribeAction
] = actions;
expect(visualizationModeChangedAction.type).toBe(VISUALIZATION_MODE_CHANGED);
expect(visualizationModeChangedAction.visualizationMode).toBe(VisualizationModes._3D);
expect(addLayerAndDescribeAction.type).toBe(ADD_LAYER_AND_DESCRIBE);
expect(addLayerAndDescribeAction.layer).toBeTruthy();
expect(addLayerAndDescribeAction.layer.type).toBe("3dtiles");
expect(addLayerAndDescribeAction.layer.url).toBe("https://server.org/name/tileset.json");
expect(addLayerAndDescribeAction.layer.title).toBe("name");
} catch (e) {
done(e);
}
done();
}, {
catalog: {
selectedService: "3dTilesCatalog",
services: {
"3dTilesCatalog": {
url: 'https://server.org/name/tileset.json',
type: '3dtiles'
}
}
}
});
});
});
});
22 changes: 17 additions & 5 deletions web/client/epics/catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ import { getSupportedFormat, getCapabilities, describeLayers } from '../api/WMS'
import CoordinatesUtils from '../utils/CoordinatesUtils';
import ConfigUtils from '../utils/ConfigUtils';
import {getCapabilitiesUrl, getLayerId, getLayerUrl} from '../utils/LayersUtils';
import { VisualizationModes } from '../utils/MapTypeUtils';
import { wrapStartStop } from '../observables/epics';
import {zoomToExtent} from "../actions/map";
import {changeVisualizationMode} from "../actions/maptype";
import CSW from '../api/CSW';

const onErrorRecordSearch = (isNewService, errObj) => {
Expand Down Expand Up @@ -151,7 +153,7 @@ export default (API) => ({
const state = store.getState();
const services = servicesSelectorWithBackgrounds(state);
const actions = layers
.filter((l, i) => !!services[sources[i]] || typeof sources[i] === 'object') // check for catalog name or object definition
.filter((l, i) => !!services?.[sources[i]] || typeof sources[i] === 'object') // check for catalog name or object definition
.map((l, i) => {
const layerOptions = get(options, i, searchOptionsSelector(state));
const source = sources[i];
Expand All @@ -160,7 +162,7 @@ export default (API) => ({
const url = service.url;
const text = layers[i];
return Rx.Observable.defer(() =>
API[format].textSearch(url, startPosition, maxRecords, text, {...layerOptions, ...service}).catch(() => ({ results: [] }))
API[format].textSearch(url, startPosition, maxRecords, text, { ...layerOptions, ...service, options: { service } }).catch(() => ({ results: [] }))
).map(r => ({ ...r, format, url, text, layerOptions }));
});
return Rx.Observable.forkJoin(actions)
Expand Down Expand Up @@ -206,12 +208,22 @@ export default (API) => ({
// return one notification for all records that have not been found
actions = [recordsNotFound(allRecordsNotFound)];
}

const layers = results
.filter(r => isObject(r[0]))
.map(r => merge({}, r[0], r[1]));

const needs3DMode = !!layers.find(r => r.type === "3dtiles");
if (needs3DMode) {
actions = [
...actions,
changeVisualizationMode(VisualizationModes._3D)
];
}
// add all layers found to the map
actions = [
...actions,
...results.filter(r => isObject(r[0])).map(r => {
return addLayer(merge({}, r[0], r[1]));
})
...layers.map(layer => addLayer(layer))
];
return Rx.Observable.from(actions);
}
Expand Down

0 comments on commit a114f7f

Please sign in to comment.