/**
### map.layer.featureStlye()
This module exports a style function for a layer
@module /layer/featureStyle
*/
/**
Creates a style function for a layer.
@function featureStyle
@description
Returns a style function for OL vector layers.
@param {Object} layer The layer object.
@property {layer-style} layer.style The layer style config.
@property {Boolean} [style.cache] The feature style should be retrieved from the feature 'Styles' property.
@returns {function} The style function for the layer.
*/
export default function featureStyle(layer) {
/**
@function Style
@description
Style functions process [vector] features and return an OL style to the layer render.
*/
return function Style(feature) {
// Style caching must be enabled with flag.
if (layer.style.cache === true) {
// Check for and return existing Styles object.
const Styles = feature.get('Styles')
if (Styles) return Styles
}
// Assign feature properties.
featureProperties(feature)
if (feature.properties === null) {
// The feature will not be visible.
return null
}
// Set style.default as feature.style.
feature.style = layer.style.default
if (Object.hasOwn(mapp.layer.themes, layer.style.theme?.type)) {
// Apply theme style to style object.
mapp.layer.themes[layer.style.theme?.type]?.(layer.style.theme, feature)
}
// The feature must not be displayed.
if (feature.style === null) return;
// Style cluster point features.
clusterStyle(feature)
// Assign highlight style if required.
highlightStyle(feature)
// Assign label style if required.
labelStyle(feature)
// Assign selected style if required.
selectedStyle(feature)
return mapp.utils.style(feature.style, feature)
}
/**
@function featureProperties
@description
Assigns feature properties based on layer configuration.
@param {Object} feature The feature object.
*/
function featureProperties(feature) {
feature.properties = feature.getProperties()
if (layer.featureSet?.size && !layer.featureSet.has(feature.properties.id)) {
feature.properties = null
return;
}
// Assign MVT feature properties from the layer featuresObject.
if (layer.featuresObject) {
if (Object.hasOwn(layer.featuresObject, feature.properties.id)) {
Object.assign(
feature.properties,
layer.featuresObject[feature.properties.id])
} else {
feature.properties = null
return;
}
}
// Check whether feature is in lookup.
if (Array.isArray(layer.featureLookup)) {
// Find feature with matching ID property in the featureLookup
const lookupFeature = layer.featureLookup.find(f => f[layer.featureLookupId || 'id'] === feature.properties.id)
// Do not style features not found in the lookup array.
if (!lookupFeature) {
feature.properties = null
return
}
// Assign feature.properties from the lookupFeature for subsequent styling
Object.assign(feature.properties, lookupFeature)
}
// Geojson / WKT features may have a properties property
if (feature.properties?.properties) {
// This shouldn't happen anymore.
console.warn(`Feature with properties.properties`)
Object.assign(feature.properties, feature.properties.properties)
delete feature.properties.properties
}
}
/**
@function clusterStyle
@description
Applies cluster style to the feature based on layer configuration.
@param {Object} feature - The feature object.
*/
function clusterStyle(feature) {
if (!feature.properties?.count) return;
if (feature.properties.count === 1) return;
if (!layer.style.cluster) return;
const clusterScale = parseFloat(layer.style.cluster.clusterScale)
// Spread cluster style into feature.style.
feature.style = {
...feature.style,
...layer.style.cluster
}
// Cluster icons will NOT scale different to single locations if the clusterScale is not set in the cluster style.
if (clusterScale) {
// The clusterScale will be added to the icon scale.
feature.style.clusterScale = layer.style.cluster.logScale ?
// A natural log will be applied to the cluster scaling.
Math.log(clusterScale) / Math.log(layer.max_size) * Math.log(feature.properties.size || feature.properties.count) :
// A fraction of the icon clusterScale will be added to the items scale for all but the biggest cluster location.
1 + (clusterScale / layer.max_size * (feature.properties.size || feature.properties.count))
}
// Setting a zoomInScale will INCREASE the scale of icons on higher zoom levels.
if (layer.style.cluster.zoomInScale) {
feature.style.zoomInScale = layer.style.cluster.zoomInScale * layer.mapview.Map.getView().getZoom()
}
// Setting a zoomOutScale will DECREASE the scale of icons on higher zoom levels.
if (layer.style.cluster.zoomOutScale) {
feature.style.zoomOutScale = layer.style.cluster.zoomOutScale / layer.mapview.Map.getView().getZoom()
}
}
/**
@function highlightStyle
@description
Applies highlight style to the feature based on layer configuration.
@param {Object} feature The feature object.
*/
function highlightStyle(feature) {
// Layer must have a highlight style.
if (!layer.style.highlight) return;
// Layer must have a highlighted feature stored as layer.highlighted.
if (!layer.highlight) return;
// The layer.highlight must be a match for the feature ID.
if (layer.highlight !== (feature.get('id') || feature.getId())) return;
feature.style = {
...feature.style,
...layer.style.highlight
}
}
/**
@function labelStyle
@description
Applies label style to the feature based on layer configuration.
@param {Object} feature The feature object.
*/
function labelStyle(feature) {
// A feature requires properties to create a label.
if (!feature.properties) return;
// Only styled features can be labelled.
if (!feature.style) return;
// The label must be displayed.
if (!layer.style.label?.display) {
delete feature.style.label
return;
}
feature.style.label = structuredClone(layer.style.label)
// Assign count value as text if label.count is truthy.
feature.style.label.text = feature.style.label.count && feature.properties.count > 1 && feature.properties.count || undefined
feature.style.label.text ??= feature.properties[feature.style.label.field] || feature.properties.label
// Delete style.label if minZoom exceeds current zoom.
feature.style.label?.minZoom > layer.mapview.Map.getView().getZoom() && delete feature.style.label;
// Delere style.label if current zoom exceeds maxZoom.
feature.style.label?.maxZoom < layer.mapview.Map.getView().getZoom() && delete feature.style.label;
}
/**
@function selectedStyle
@description
Applies selected style to the feature based on layer configuration.
@param {Object} feature The feature object.
*/
function selectedStyle(feature) {
// Return before lookup in mapview.locations object.
if (layer.style.selected === undefined) return;
// Check whether the feature referenced in mapview.locations
if (layer.mapview?.locations[`${layer.key}!${feature.properties.id}`]) {
feature.style = layer.style.selected
}
}
}