Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

For #111 : dynamic layers in Wegue format #113

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 69 additions & 59 deletions src/components/ol/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,108 +35,119 @@ export default {
}
},
mounted () {
var me = this;
// Make the OL map accessible for Mapable mixin even 'ol-map-mounted' has
// already been fired. Don not use directly in cmps, use Mapable instead.
Vue.prototype.$map = me.map;
Vue.prototype.$map = this.map;
// Send the event 'ol-map-mounted' with the OL map as payload
WguEventBus.$emit('ol-map-mounted', me.map);
WguEventBus.$emit('ol-map-mounted', this.map);

// resize the map, so it fits to parent
window.setTimeout(() => {
me.map.setTarget(document.getElementById('ol-map-container'));
me.map.updateSize();
// resize the map, so it fits to parent, may need to wait
// until map container element is ready.
const timer = setInterval(() => {
const mapTarget = document.getElementById('ol-map-container');
if (!mapTarget) {
return;
}
clearInterval(timer);
this.map.setTarget(mapTarget);
this.map.updateSize();

// adjust the bg color of the OL buttons (like zoom, rotate north, ...)
me.setOlButtonColor();
this.setOlButtonColor();

// initialize map hover functionality
me.setupMapHover();
}, 200);
this.setupMapHover();
}, 100);
},
created () {
var me = this;

async created () {
// make map rotateable according to property
const interactions = defaultInteractions({
altShiftDragRotate: me.rotateableMap,
pinchRotate: me.rotateableMap
altShiftDragRotate: this.rotateableMap,
pinchRotate: this.rotateableMap
});
let controls = [
new Zoom(),
new Attribution({
collapsible: me.collapsibleAttribution
collapsible: this.collapsibleAttribution
})
];
// add a button control to reset rotation to 0, if map is rotateable
if (me.rotateableMap) {
if (this.rotateableMap) {
controls.push(new RotateControl());
}

// Optional projection (EPSG) definitions for Proj4
if (me.projectionDefs) {
if (this.projectionDefs) {
// Add all (array of array)
proj4.defs(me.projectionDefs);
proj4.defs(this.projectionDefs);
// Register with OpenLayers
olproj4(proj4);
}

// Projection for Map, default is Web Mercator
if (!me.projection) {
me.projection = {code: 'EPSG:3857', units: 'm'}
if (!this.projection) {
this.projection = {code: 'EPSG:3857', units: 'm'}
}
const projection = new Projection(me.projection);
const projection = new Projection(this.projection);

me.map = new Map({
this.map = new Map({
layers: [],
controls: controls,
interactions: interactions,
view: new View({
center: me.center || [0, 0],
zoom: me.zoom,
center: this.center || [0, 0],
zoom: this.zoom,
projection: projection
})
});

// create layers from config and add them to map
const layers = me.createLayers();
me.map.getLayers().extend(layers);
const layers = await this.createLayers();
this.map.getLayers().extend(layers);
},

methods: {
/**
* Creates the OL layers due to the "mapLayers" array in app config.
* @return {ol.layer.Base[]} Array of OL layer instances
*/
createLayers () {
const me = this;
let layers = [];
const appConfig = this.$appConfig;
const mapLayersConfig = appConfig.mapLayers || [];
mapLayersConfig.reverse().forEach(function (lConf) {
let layer = LayerFactory.getInstance(lConf);
layers.push(layer);

async createLayers () {
const addInteraction = (layer) => {
// if layer is selectable register a select interaction
if (lConf.selectable) {
const selectClick = new SelectInteraction({
layers: [layer]
});
// forward an event if feature selection changes
selectClick.on('select', function (evt) {
// TODO use identifier for layer (once its implemented)
WguEventBus.$emit(
'map-selectionchange',
layer.get('lid'),
evt.selected,
evt.deselected
);
});
// register/activate interaction on map
me.map.addInteraction(selectClick);
if (layer.get('selectable') === false) {
return;
}
});
const selectClick = new SelectInteraction({
layers: [layer]
});
// forward an event if feature selection changes
selectClick.on('select', function (evt) {
// TODO use identifier for layer (once its implemented)
WguEventBus.$emit(
'map-selectionchange',
layer.get('lid'),
evt.selected,
evt.deselected
);
});
// register/activate interaction on map
this.map.addInteraction(selectClick);
};

let layers = [];
const mapLayersConfig = this.$appConfig.mapLayers;
await Promise.all(mapLayersConfig.reverse().map(async lConf => {
let layersToAdd = await LayerFactory.getInstance(lConf);
// One layer definition can lead to several layer instances being created
if (Array.isArray(layersToAdd)) {
// Reverse like main config to have Layers added in right stacking order.
layersToAdd = layersToAdd.reverse();
} else {
layersToAdd = [layersToAdd];
}
layersToAdd.forEach(layer => addInteraction(layer));
layers.push(...layersToAdd);
}));
return layers;
},
/**
Expand Down Expand Up @@ -178,29 +189,28 @@ export default {
* 'hoverAttribute' if the layer is configured as 'hoverable'
*/
setupMapHover () {
const me = this;
const map = me.map;
const map = this.map;
let overlayEl;

// create a span to show map tooltip
overlayEl = document.createElement('span');
overlayEl.classList.add('wgu-hover-tooltiptext');
map.getTarget().append(overlayEl);

me.overlayEl = overlayEl;
this.overlayEl = overlayEl;

// wrap the tooltip span in a OL overlay and add it to map
me.overlay = new Overlay({
this.overlay = new Overlay({
element: overlayEl,
autoPan: true,
autoPanAnimation: {
duration: 250
}
});
map.addOverlay(me.overlay);
map.addOverlay(this.overlay);

// show tooltip if a hoverable feature gets hit with the mouse
map.on('pointermove', me.onPointerMove, me);
map.on('pointermove', this.onPointerMove, this);
},

/**
Expand Down
23 changes: 21 additions & 2 deletions src/factory/Layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export const LayerFactory = {
* @param {Object} lConf Layer config object
* @return {ol.layer.Base} OL layer instance
*/
getInstance (lConf) {
async getInstance (lConf) {
// apply LID (Layer ID) if not existent
if (!lConf.lid) {
var now = new Date();
Expand All @@ -53,6 +53,8 @@ export const LayerFactory = {
return this.createVectorLayer(lConf);
} else if (lConf.type === 'VECTORTILE') {
return this.createVectorTileLayer(lConf);
} else if (lConf.type === 'LAYERCOLLECTION') {
return this.createLayersFromCollection(lConf);
} else {
return null;
}
Expand All @@ -69,6 +71,7 @@ export const LayerFactory = {
name: lConf.name,
lid: lConf.lid,
displayInLayerList: lConf.displayInLayerList,
selectable: lConf.selectable || false,
extent: lConf.extent,
visible: lConf.visible,
opacity: lConf.opacity,
Expand Down Expand Up @@ -97,6 +100,7 @@ export const LayerFactory = {
name: lConf.name,
lid: lConf.lid,
displayInLayerList: lConf.displayInLayerList,
selectable: lConf.selectable || false,
visible: lConf.visible,
opacity: lConf.opacity,
source: new XyzSource({
Expand All @@ -118,6 +122,7 @@ export const LayerFactory = {
name: lConf.name,
lid: lConf.lid,
displayInLayerList: lConf.displayInLayerList,
selectable: lConf.selectable || false,
visible: lConf.visible,
opacity: lConf.opacity,
source: new OsmSource()
Expand All @@ -137,6 +142,7 @@ export const LayerFactory = {
name: lConf.name,
lid: lConf.lid,
displayInLayerList: lConf.displayInLayerList,
selectable: lConf.selectable || false,
extent: lConf.extent,
visible: lConf.visible,
opacity: lConf.opacity,
Expand Down Expand Up @@ -164,6 +170,7 @@ export const LayerFactory = {
name: lConf.name,
lid: lConf.lid,
displayInLayerList: lConf.displayInLayerList,
selectable: lConf.selectable || false,
visible: lConf.visible,
opacity: lConf.opacity,
source: new VectorTileSource({
Expand All @@ -177,6 +184,18 @@ export const LayerFactory = {
});

return vtLayer;
}
},

/**
* Returns an array of Wegue Layer objects from given config.
*
* @param {Object} lConf Wegue Layer list config object
* @return {Array} array of layer instances
*/
async createLayersFromCollection (lConf) {
const response = await (await fetch(lConf.url)).json();
return Promise.all(response.map(async layerDef => {
return this.getInstance(layerDef);
}));
}
}
5 changes: 4 additions & 1 deletion static/app-conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@
"fillColor": "rgba(20,20,20,0.1)"
}
},

{
"type": "LAYERCOLLECTION",
"url": "./static/layer-collection.json"
},
{
"type": "OSM",
"lid": "osm-bg",
Expand Down
37 changes: 37 additions & 0 deletions static/layer-collection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"type": "VECTOR",
"lid": "portuguese_pois",
"name": "Portuguese POIs",
"url": "https://demo.pygeoapi.io/master/collections/ogr_gpkg_poi/items",
"formatConfig": {

},
"format": "GeoJSON",
"visible": true,
"selectable": true,
"displayInLayerList": true,
"style": {
"radius": 6,
"strokeColor": "blue",
"strokeWidth": 2,
"fillColor": "rgba(0,0,100,0.5)"
}
},
{
"type": "WMS",
"lid": "au_administrativeunit",
"name": "Dutch Administrative Units",
"format": "image/png",
"layers": "AU.AdministrativeUnit",
"url": "https://geodata.nationaalgeoregister.nl/inspire/au/wms",
"transparent": true,
"singleTile": false,
"projection": "EPSG:3857",
"attribution": "Dutch Kadaster",
"isBaseLayer": false,
"visible": false,
"selectable": true,
"displayInLayerList": true
}
]
28 changes: 24 additions & 4 deletions test/unit/specs/components/ol/Map.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ describe('ol/Map.vue', () => {
vm = comp.vm;
});

it('createLayers returns always an array', () => {
it('createLayers returns always an array', async () => {
// mock a map layer config
Vue.prototype.$appConfig = {mapLayers: [{
'type': 'OSM',
Expand All @@ -76,12 +76,32 @@ describe('ol/Map.vue', () => {
'selectable': false,
'displayInLayerList': true}]
};
const layers = vm.createLayers();
const layers = await vm.createLayers();
expect(layers).to.be.an('array');
expect(layers.length).to.equal(1);
});

it('createLayers registers a select interaction if configured', () => {
it('createLayers expands LAYERCOLLECTION Layer type', async () => {
// mock a map layer config
Vue.prototype.$appConfig = {mapLayers: [{
'type': 'OSM',
'lid': 'osm-bg',
'name': 'OSM',
'isBaseLayer': false,
'visible': true,
'selectable': false,
'displayInLayerList': true}, {
'type': 'LAYERCOLLECTION',
// should change URL to Wegue GH when ready
'url': 'https://raw.githubusercontent.com/Geolicious/wegue/111-dynlayers-wegueformat/static/layer-collection.json'}]
};
const layers = await vm.createLayers();
expect(layers).to.be.an('array');
// OSM (1 layer) and LAYERCOLLECTION (2 layers)
expect(layers.length).to.equal(3);
});

it('createLayers registers a select interaction if configured', async () => {
// mock a map layer config
Vue.prototype.$appConfig = {mapLayers: [{
'type': 'OSM',
Expand All @@ -92,7 +112,7 @@ describe('ol/Map.vue', () => {
'selectable': true,
'displayInLayerList': true}]
};
vm.createLayers();
await vm.createLayers();
let selectIa;
vm.map.getInteractions().forEach((ia) => {
if (ia instanceof SelectInteraction) {
Expand Down
Loading