Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
130 changes: 22 additions & 108 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions src/components/attributeTable/AttributeTableWin.vue
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,12 @@ export default {
* Reactive property to return the OpenLayers vector layers to be shown in the selection menu.
*/
displayedLayers () {
return this.layers
if (!this.layers) {
return [];
}
return this.layers.getArray()
.filter(layer =>
layer instanceof VectorLayer &&
layer.toRaw() instanceof VectorLayer &&
layer.get('lid') !== 'wgu-measure-layer' &&
layer.get('lid') !== 'wgu-geolocator-layer'
)
Expand Down
5 changes: 4 additions & 1 deletion src/components/bglayerswitcher/BgLayerList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ export default {
* Reactive property to return the OpenLayers layers marked as 'isBaseLayer'.
*/
displayedLayers () {
return this.layers
if (!this.layers) {
return [];
}
return this.layers.getArray()
.filter(layer => layer.get('isBaseLayer'))
.reverse();
},
Expand Down
5 changes: 4 additions & 1 deletion src/components/bglayerswitcher/BgLayerSwitcher.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ export default {
* which is marked as 'isBaseLayer'.
*/
show () {
return this.layers
if (!this.layers) {
return false;
}
return this.layers.getArray()
.filter(layer => layer.get('isBaseLayer'))
.length > 1;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/bglayerswitcher/LayerPreviewImage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default {
*/
previewURL () {
return this.layer.get('previewImage') || LayerPreview.getUrl(
this.layer,
this.layer.toRaw(),
this.mapView.getCenter(),
this.mapView.getResolution(),
this.mapView.getProjection()
Expand Down
3 changes: 2 additions & 1 deletion src/components/layerlist/LayerLegendImage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ export default {
...this.layer.get('legendOptions')
};
return legendUtil.getUrl(
this.layer, this.resolution, options, this.layer.get('legendUrl'));
this.layer.toRaw(), this.resolution, options,
this.layer.get('legendUrl'));
}
}
};
Expand Down
13 changes: 8 additions & 5 deletions src/components/layerlist/LayerList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,15 @@ export default {
}
},
computed: {
/**
* Reactive property to return the OpenLayers layers to be shown in the control.
* Remarks: The 'displayInLayerList' attribute should default to true per convention.
*/
/**
* Reactive property to return the OpenLayers layers to be shown in the control.
* Remarks: The 'displayInLayerList' attribute should default to true per convention.
*/
displayedLayers () {
return this.layers
if (!this.layers) {
return [];
}
return this.layers.getArray()
.filter(layer => layer.get('displayInLayerList') !== false && !layer.get('isBaseLayer'))
.reverse();
}
Expand Down
10 changes: 7 additions & 3 deletions src/components/overviewmap/OverviewMapPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export default {
const panel = this.$refs.overviewmapPanel;
if (this.map && panel && !this.overviewMap) {
this.overviewMap = new OverviewMapController(this.map, panel.$el, this.$props);
this.overviewMap.setLayer(this.selectedBgLayer.toRaw());
}
},
/**
Expand All @@ -64,7 +65,10 @@ export default {
* this returns the first in the list of background layers.
*/
selectedBgLayer () {
return this.layers
if (!this.layers) {
return {};
}
return this.layers.getArray()
.filter(layer => layer.get('isBaseLayer'))
.reverse()
.find(layer => layer.getVisible());
Expand All @@ -86,9 +90,9 @@ export default {
/**
* Watch for background layer selection change.
*/
selectedBgLayer () {
selectedBgLayer (newLayer) {
if (this.overviewMap) {
this.overviewMap.setLayer(this.selectedBgLayer);
this.overviewMap.setLayer(newLayer.toRaw());
}
}
}
Expand Down
20 changes: 9 additions & 11 deletions src/composables/Map.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ref, shallowRef, computed } from 'vue';
import { shallowRef, computed } from 'vue';
import { LayerCollectionProxy } from '@/util/Layer';

/**
* Composable which encapsulate OL map management.
*/
const map = shallowRef();
const layers = ref([]);
const layers = shallowRef();

/**
* Init function of the composable.
Expand All @@ -13,11 +14,7 @@ const layers = ref([]);
*/
export function bindMap (olMap) {
map.value = olMap;
layers.value = map.value.getLayers().getArray();

map.value.getLayers().on('change:length', (event) => {
layers.value = [...event.target.getArray()];
});
layers.value = new LayerCollectionProxy(olMap.getLayers());
};

/**
Expand All @@ -26,7 +23,10 @@ export function bindMap (olMap) {
* disable map and layers reactivity.
*/
export function unbindMap () {
layers.value = [];
if (layers.value) {
layers.value.destroy();
layers.value = undefined;
}
map.value = undefined;
};

Expand All @@ -37,8 +37,6 @@ export function unbindMap () {
export function useMap () {
return {
map: computed(() => map.value),
layers: computed(() => {
return layers.value;
})
layers: computed(() => layers.value)
};
};
219 changes: 219 additions & 0 deletions src/util/Layer.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ViewAnimationUtil from './ViewAnimation';
import { reactive, toRaw, markRaw } from 'vue'

/**
* Util class for OL layers
Expand Down Expand Up @@ -57,3 +58,221 @@ const LayerUtil = {
}

export default LayerUtil;

/**
* Transparent proxy around an OpenLayers layer to be used in Vue components
* when reactive layer properties are required.
*/
export class LayerProxy {
/**
* @param {ol.layer.Base} layer OL layer
*/
constructor (layer) {
this.layer = layer;
this.properties = reactive({});

// Map getXXX methods declared in ol/layer/Base to property keys.
// For mappings see ol/layer/Property.js.
this.getterMap = {
getOpacity: 'opacity',
getVisible: 'visible',
getExtent: 'extent',
getZIndex: 'zIndex',
getMaxResolution: 'maxResolution',
getMinResolution: 'minResolution',
getMaxZoom: 'maxZoom',
getMinZoom: 'minZoom'
};

// Set up listener to detect any property changes
this.propertyChangeListener = (event) => {
const key = event.key;
const value = this.layer.get(key);

if (value === undefined) {
if (key in this.properties) {
delete this.properties[key];
}
} else {
this.properties[key] = value;
}
};
this.layer.on('propertychange', this.propertyChangeListener);

// Track existing properties
Object.keys(layer.getProperties()).forEach(key => {
this.properties[key] = layer.get(key);
});

// Forward everything transparently to the underlying OL layer. The get()
// and getProperties() methods are trapped and handled by the proxy.
// Remarks: Neither set() nor setProperties() have to be handled. Property
// setters operate on the OL layer and then get synced into the proxy via
// observables.
const proxy = new Proxy(this, {
get: function (target, prop, receiver) {
// Intercept getXXX() calls and map to reactive properties
if (prop in target.getterMap) {
const key = target.getterMap[prop];
return () => target.get(key);
}

// Forward OL layer API calls except get/getProperties
if (prop in target.layer && !['get', 'getProperties'].includes(prop)) {
const p = target.layer[prop];
return (typeof p === 'function') ? p.bind(target.layer) : p;
}
return Reflect.get(target, prop, receiver);
}
});

// Avoid Vue wrapping the proxy again.
return markRaw(proxy);
}

/**
* Gets a value of the layer.
* @param {String} key Key name.
* @returns {any} Value.
*/
get (key) {
return this.properties[key];
}

/**
* Get all property names and values of the layer.
* @returns {Object} Object.
*/
getProperties () {
return this.properties;
}

/**
* Get the raw OL layer object wrapped by this proxy.
* @returns {ol.layer.Base} OL layer
*/
toRaw () {
return toRaw(this.layer);
}

/**
* Destroy the proxy object. This must be invoked before the proxy goes out
* of scope to prevent dangling change notifications.
*/
destroy () {
this.layer.un('propertychange', this.propertyChangeListener);
}
}

/**
* Transparent proxy around an OpenLayers collection for layers to be used in
* Vue components when reactive layer properties are required.
*/
export class LayerCollectionProxy {
/**
* @param {ol.Collection<ol.layer.Base>} collection OL collection of layers
*/
constructor (collection) {
this.collection = reactive(collection);
this.layerProxies = reactive([]);

const createLayerProxy = (layer) => new LayerProxy(layer);

// Sync against the underlying collection while retaining the order of
// elements.
// Remarks: A layer proxy must be destroyed before it goes out of scope.
// To minimize the overhead of creating layerProxies preserve existing
// instances by merging.
this.syncLayers = () => {
const newLayerProxies = [];
this.collection.forEach(layer => {
let layerProxy = this.layerProxies.find(
proxy => proxy.toRaw() === toRaw(layer));
if (!layerProxy) {
layerProxy = createLayerProxy(layer);
}
newLayerProxies.push(layerProxy);
});

this.layerProxies.forEach(layerProxy => {
if (!newLayerProxies.includes(layerProxy)) {
layerProxy.destroy();
}
});

this.layerProxies.splice(0);
this.layerProxies.push(...newLayerProxies);
};

this.syncLayers();

this.collection.on('change:length', this.syncLayers);

// Forward everything transparently to the underlying OL collection.
// The forEach() and getArray() and item() methods are trapped and handled
// by the proxy.
// Remarks: Methods which alter the collection are not handled.
// These operate on the OL collection and changes get synced into the
// proxy via observables. The methods pop(), push(), remove(),
// removeAt(), setAt() will operate on OL base layer arguments. Therefore
// returned objects by these methods will not properly support reactivity.
const proxy = new Proxy(this, {
get: function (target, prop, receiver) {
if (prop in target.collection &&
!['forEach', 'getArray', 'item'].includes(prop)) {
const p = target.collection[prop];
return (typeof p === 'function') ? p.bind(target.collection) : p;
}
return Reflect.get(target, prop, receiver);
}
});

// Avoid Vue wrapping the proxy again.
return markRaw(proxy);
}

/**
* Iterate over each element, calling the provided callback.
* @param {function} f The function to call for every element. This function
* takes 3 arguments (the element, the index and the array). The return value
* is ignored.
*/
forEach (f) {
this.layerProxies.forEach(f)
}

/**
* Get an array of LayerProxy objects for all layers in the collection.
* @returns {Array<LayerProxy>} Array of LayerProxy objects.
*/
getArray () {
return this.layerProxies;
}

/**
* Get the LayerProxy at the provided index.
* @param {Number} index Index.
* @returns Element.
*/
item (index) {
return this.layerProxies[index];
}

/**
* Get the raw collection object wrapped by this proxy.
* @returns {ol.Collection<ol.layer.Base>} OL collection of layers
*/
toRaw () {
return toRaw(this.collection);
}

/**
* Destroy the proxy object. This must be invoked before the proxy goes out of scope.
*/
destroy () {
this.collection.un('change:length', this.syncLayers);
this.layerProxies.forEach(layerProxy => {
layerProxy.destroy();
});
}
}
Loading