UNPKG

ol

Version:

OpenLayers mapping library

580 lines (531 loc) • 15.1 kB
/** * @module ol/style/Style */ import {assert} from '../asserts.js'; import CircleStyle from './Circle.js'; import Fill from './Fill.js'; import Stroke from './Stroke.js'; /** * Defines how symbols and text are decluttered on layers ith `declutter` set to `true` * **declutter**: Overlapping symbols and text are decluttered. * **obstacle**: Symbols and text are rendered, but serve as obstacle for subsequent attempts * to place a symbol or text at the same location. * **none**: No decluttering is done. * * @typedef {"declutter"|"obstacle"|"none"} DeclutterMode */ /** * A function that takes a {@link module:ol/Feature~Feature} and a `{number}` * representing the view's resolution. The function should return a * {@link module:ol/style/Style~Style} or an array of them. This way e.g. a * vector layer can be styled. If the function returns `undefined`, the * feature will not be rendered. * * @typedef {function(import("../Feature.js").FeatureLike, number):(Style|Array<Style>|void)} StyleFunction */ /** * A {@link Style}, an array of {@link Style}, or a {@link StyleFunction}. * @typedef {Style|Array<Style>|StyleFunction} StyleLike */ /** * A function that takes a {@link module:ol/Feature~Feature} as argument and returns an * {@link module:ol/geom/Geometry~Geometry} that will be rendered and styled for the feature. * * @typedef {function(import("../Feature.js").FeatureLike): * (import("../geom/Geometry.js").default|import("../render/Feature.js").default|undefined)} GeometryFunction */ /** * Custom renderer function. Takes two arguments: * * 1. The pixel coordinates of the geometry in GeoJSON notation. * 2. The {@link module:ol/render~State} of the layer renderer. * * @typedef {function((import("../coordinate.js").Coordinate|Array<import("../coordinate.js").Coordinate>|Array<Array<import("../coordinate.js").Coordinate>>|Array<Array<Array<import("../coordinate.js").Coordinate>>>),import("../render.js").State): void} RenderFunction */ /** * @typedef {Object} Options * @property {string|import("../geom/Geometry.js").default|GeometryFunction} [geometry] Feature property or geometry * or function returning a geometry to render for this style. * @property {import("./Fill.js").default} [fill] Fill style. * @property {import("./Image.js").default} [image] Image style. * @property {RenderFunction} [renderer] Custom renderer. When configured, `fill`, `stroke` and `image` will be * ignored, and the provided function will be called with each render frame for each geometry. * @property {RenderFunction} [hitDetectionRenderer] Custom renderer for hit detection. If provided will be used * in hit detection rendering. * @property {import("./Stroke.js").default} [stroke] Stroke style. * @property {import("./Text.js").default} [text] Text style. * @property {number} [zIndex] Z index. */ /** * @classdesc * Container for vector feature rendering styles. Any changes made to the style * or its children through `set*()` methods will not take effect until the * feature or layer that uses the style is re-rendered. * * ## Feature styles * * If no style is defined, the following default style is used: * ```js * import {Circle, Fill, Stroke, Style} from 'ol/style.js'; * * const fill = new Fill({ * color: 'rgba(255,255,255,0.4)', * }); * const stroke = new Stroke({ * color: '#3399CC', * width: 1.25, * }); * const styles = [ * new Style({ * image: new Circle({ * fill: fill, * stroke: stroke, * radius: 5, * }), * fill: fill, * stroke: stroke, * }), * ]; * ``` * * A separate editing style has the following defaults: * ```js * import {Circle, Fill, Stroke, Style} from 'ol/style.js'; * * const styles = {}; * const white = [255, 255, 255, 1]; * const blue = [0, 153, 255, 1]; * const width = 3; * styles['Polygon'] = [ * new Style({ * fill: new Fill({ * color: [255, 255, 255, 0.5], * }), * }), * ]; * styles['MultiPolygon'] = * styles['Polygon']; * styles['LineString'] = [ * new Style({ * stroke: new Stroke({ * color: white, * width: width + 2, * }), * }), * new Style({ * stroke: new Stroke({ * color: blue, * width: width, * }), * }), * ]; * styles['MultiLineString'] = styles['LineString']; * * styles['Circle'] = styles['Polygon'].concat( * styles['LineString'] * ); * * styles['Point'] = [ * new Style({ * image: new Circle({ * radius: width * 2, * fill: new Fill({ * color: blue, * }), * stroke: new Stroke({ * color: white, * width: width / 2, * }), * }), * zIndex: Infinity, * }), * ]; * styles['MultiPoint'] = * styles['Point']; * styles['GeometryCollection'] = * styles['Polygon'].concat( * styles['LineString'], * styles['Point'] * ); * ``` * * @api */ class Style { /** * @param {Options} [options] Style options. */ constructor(options) { options = options || {}; /** * @private * @type {string|import("../geom/Geometry.js").default|GeometryFunction|null} */ this.geometry_ = null; /** * @private * @type {!GeometryFunction} */ this.geometryFunction_ = defaultGeometryFunction; if (options.geometry !== undefined) { this.setGeometry(options.geometry); } /** * @private * @type {import("./Fill.js").default|null} */ this.fill_ = options.fill !== undefined ? options.fill : null; /** * @private * @type {import("./Image.js").default|null} */ this.image_ = options.image !== undefined ? options.image : null; /** * @private * @type {RenderFunction|null} */ this.renderer_ = options.renderer !== undefined ? options.renderer : null; /** * @private * @type {RenderFunction|null} */ this.hitDetectionRenderer_ = options.hitDetectionRenderer !== undefined ? options.hitDetectionRenderer : null; /** * @private * @type {import("./Stroke.js").default|null} */ this.stroke_ = options.stroke !== undefined ? options.stroke : null; /** * @private * @type {import("./Text.js").default|null} */ this.text_ = options.text !== undefined ? options.text : null; /** * @private * @type {number|undefined} */ this.zIndex_ = options.zIndex; } /** * Clones the style. * @return {Style} The cloned style. * @api */ clone() { let geometry = this.getGeometry(); if (geometry && typeof geometry === 'object') { geometry = /** @type {import("../geom/Geometry.js").default} */ ( geometry ).clone(); } return new Style({ geometry: geometry ?? undefined, fill: this.getFill() ? this.getFill().clone() : undefined, image: this.getImage() ? this.getImage().clone() : undefined, renderer: this.getRenderer() ?? undefined, stroke: this.getStroke() ? this.getStroke().clone() : undefined, text: this.getText() ? this.getText().clone() : undefined, zIndex: this.getZIndex(), }); } /** * Get the custom renderer function that was configured with * {@link #setRenderer} or the `renderer` constructor option. * @return {RenderFunction|null} Custom renderer function. * @api */ getRenderer() { return this.renderer_; } /** * Sets a custom renderer function for this style. When set, `fill`, `stroke` * and `image` options of the style will be ignored. * @param {RenderFunction|null} renderer Custom renderer function. * @api */ setRenderer(renderer) { this.renderer_ = renderer; } /** * Sets a custom renderer function for this style used * in hit detection. * @param {RenderFunction|null} renderer Custom renderer function. * @api */ setHitDetectionRenderer(renderer) { this.hitDetectionRenderer_ = renderer; } /** * Get the custom renderer function that was configured with * {@link #setHitDetectionRenderer} or the `hitDetectionRenderer` constructor option. * @return {RenderFunction|null} Custom renderer function. * @api */ getHitDetectionRenderer() { return this.hitDetectionRenderer_; } /** * Get the geometry to be rendered. * @return {string|import("../geom/Geometry.js").default|GeometryFunction|null} * Feature property or geometry or function that returns the geometry that will * be rendered with this style. * @api */ getGeometry() { return this.geometry_; } /** * Get the function used to generate a geometry for rendering. * @return {!GeometryFunction} Function that is called with a feature * and returns the geometry to render instead of the feature's geometry. * @api */ getGeometryFunction() { return this.geometryFunction_; } /** * Get the fill style. * @return {import("./Fill.js").default|null} Fill style. * @api */ getFill() { return this.fill_; } /** * Set the fill style. * @param {import("./Fill.js").default|null} fill Fill style. * @api */ setFill(fill) { this.fill_ = fill; } /** * Get the image style. * @return {import("./Image.js").default|null} Image style. * @api */ getImage() { return this.image_; } /** * Set the image style. * @param {import("./Image.js").default} image Image style. * @api */ setImage(image) { this.image_ = image; } /** * Get the stroke style. * @return {import("./Stroke.js").default|null} Stroke style. * @api */ getStroke() { return this.stroke_; } /** * Set the stroke style. * @param {import("./Stroke.js").default|null} stroke Stroke style. * @api */ setStroke(stroke) { this.stroke_ = stroke; } /** * Get the text style. * @return {import("./Text.js").default|null} Text style. * @api */ getText() { return this.text_; } /** * Set the text style. * @param {import("./Text.js").default} text Text style. * @api */ setText(text) { this.text_ = text; } /** * Get the z-index for the style. * @return {number|undefined} ZIndex. * @api */ getZIndex() { return this.zIndex_; } /** * Set a geometry that is rendered instead of the feature's geometry. * * @param {string|import("../geom/Geometry.js").default|GeometryFunction|null} geometry * Feature property or geometry or function returning a geometry to render * for this style. * @api */ setGeometry(geometry) { if (typeof geometry === 'function') { this.geometryFunction_ = geometry; } else if (typeof geometry === 'string') { this.geometryFunction_ = function (feature) { return /** @type {import("../geom/Geometry.js").default} */ ( feature.get(geometry) ); }; } else if (!geometry) { this.geometryFunction_ = defaultGeometryFunction; } else if (geometry !== undefined) { this.geometryFunction_ = function () { return /** @type {import("../geom/Geometry.js").default} */ (geometry); }; } this.geometry_ = geometry; } /** * Set the z-index. * * @param {number|undefined} zIndex ZIndex. * @api */ setZIndex(zIndex) { this.zIndex_ = zIndex; } } /** * Convert the provided object into a style function. Functions passed through * unchanged. Arrays of Style or single style objects wrapped in a * new style function. * @param {StyleFunction|Array<Style>|Style} obj * A style function, a single style, or an array of styles. * @return {StyleFunction} A style function. */ export function toFunction(obj) { let styleFunction; if (typeof obj === 'function') { styleFunction = obj; } else { /** * @type {Array<Style>} */ let styles; if (Array.isArray(obj)) { styles = obj; } else { assert( typeof (/** @type {?} */ (obj).getZIndex) === 'function', 'Expected an `Style` or an array of `Style`', ); const style = /** @type {Style} */ (obj); styles = [style]; } styleFunction = function () { return styles; }; } return styleFunction; } /** * @type {Array<Style>|null} */ let defaultStyles = null; /** * @param {import("../Feature.js").FeatureLike} feature Feature. * @param {number} resolution Resolution. * @return {Array<Style>} Style. */ export function createDefaultStyle(feature, resolution) { // We don't use an immediately-invoked function // and a closure so we don't get an error at script evaluation time in // browsers that do not support Canvas. (import("./Circle.js").CircleStyle does // canvas.getContext('2d') at construction time, which will cause an.error // in such browsers.) if (!defaultStyles) { const fill = new Fill({ color: 'rgba(255,255,255,0.4)', }); const stroke = new Stroke({ color: '#3399CC', width: 1.25, }); defaultStyles = [ new Style({ image: new CircleStyle({ fill: fill, stroke: stroke, radius: 5, }), fill: fill, stroke: stroke, }), ]; } return defaultStyles; } /** * Default styles for editing features. * @return {Object<import("../geom/Geometry.js").Type, Array<Style>>} Styles */ export function createEditingStyle() { /** @type {Object<import("../geom/Geometry.js").Type, Array<Style>>} */ const styles = {}; const white = [255, 255, 255, 1]; const blue = [0, 153, 255, 1]; const width = 3; styles['Polygon'] = [ new Style({ fill: new Fill({ color: [255, 255, 255, 0.5], }), }), ]; styles['MultiPolygon'] = styles['Polygon']; styles['LineString'] = [ new Style({ stroke: new Stroke({ color: white, width: width + 2, }), }), new Style({ stroke: new Stroke({ color: blue, width: width, }), }), ]; styles['MultiLineString'] = styles['LineString']; styles['Circle'] = styles['Polygon'].concat(styles['LineString']); styles['Point'] = [ new Style({ image: new CircleStyle({ radius: width * 2, fill: new Fill({ color: blue, }), stroke: new Stroke({ color: white, width: width / 2, }), }), zIndex: Infinity, }), ]; styles['MultiPoint'] = styles['Point']; styles['GeometryCollection'] = styles['Polygon'].concat( styles['LineString'], styles['Point'], ); return styles; } /** * Function that is called with a feature and returns its default geometry. * @param {import("../Feature.js").FeatureLike} feature Feature to get the geometry for. * @return {import("../geom/Geometry.js").default|import("../render/Feature.js").default|undefined} Geometry to render. */ function defaultGeometryFunction(feature) { return feature.getGeometry(); } export default Style;