// Copyright (c) 2015 - 2017 Uber Technologies, Inc. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import { Accessor, AccessorFunction, Color, CompositeLayer, CompositeLayerProps, createIterable, Layer, LayerDataSource, LayersList, log, Unit, Material, UpdateParameters, DefaultProps } from '@deck.gl/core'; import SolidPolygonLayer from '../solid-polygon-layer/solid-polygon-layer'; import PathLayer from '../path-layer/path-layer'; import * as Polygon from '../solid-polygon-layer/polygon'; import {replaceInRange} from '../utils'; /** * All properties supported by `PolygonLayer`. */ export type PolygonLayerProps = _PolygonLayerProps & CompositeLayerProps; /** * Properties added by `PolygonLayer`. */ type _PolygonLayerProps = { data: LayerDataSource; /** * Whether to draw an outline around the polygon (solid fill). * * Note that both the outer polygon as well the outlines of any holes will be drawn. * * @default true */ stroked?: boolean; /** * Whether to draw a filled polygon (solid fill). * * Note that only the area between the outer polygon and any holes will be filled. * * @default true */ filled?: boolean; /** * Whether to extrude the polygons. * * Based on the elevations provided by the `getElevation` accessor. * * If set to `false`, all polygons will be flat, this generates less geometry and is faster * than simply returning 0 from getElevation. * * @default false */ extruded?: boolean; /** * Elevation multiplier. * * The final elevation is calculated by `elevationScale * getElevation(d)`. * `elevationScale` is a handy property to scale all elevation without updating the data. * * @default 1 */ elevationScale?: number; /** * Whether to generate a line wireframe of the hexagon. * * The outline will have "horizontal" lines closing the top and bottom polygons and a vertical * line (a "strut") for each vertex on the polygon. * * @default false */ wireframe?: boolean; /** * The units of the line width, one of `meters`, `common`, and `pixels`. * * @default 'meters' * @see Unit. */ lineWidthUnits?: Unit; /** * The line width multiplier that multiplied to all outlines of `Polygon` and `MultiPolygon` * features if the stroked attribute is true. * * @default 1 */ lineWidthScale?: number; /** * The minimum line width in pixels. * * @default 0 */ lineWidthMinPixels?: number; /** * The maximum line width in pixels * * @default Number.MAX_SAFE_INTEGER */ lineWidthMaxPixels?: number; /** * Type of joint. If `true`, draw round joints. Otherwise draw miter joints. * * @default false */ lineJointRounded?: boolean; /** * The maximum extent of a joint in ratio to the stroke width. * * Only works if `lineJointRounded` is false. * * @default 4 */ lineMiterLimit?: number; lineDashJustified?: boolean; /** Called on each object in the data stream to retrieve its corresponding polygon. */ getPolygon?: AccessorFunction; /** * Fill collor value or accessor. * * @default [0, 0, 0, 255] */ getFillColor?: Accessor; /** * Line color value or accessor. * * @default [0, 0, 0, 255] */ getLineColor?: Accessor; /** * Line width value or accessor. * * @default 1 */ getLineWidth?: Accessor; /** * Elevation valur or accessor. * * Only used if `extruded: true`. * * @default 1000 */ getElevation?: Accessor; /** * This property has been moved to `PathStyleExtension`. * * @deprecated */ getLineDashArray?: Accessor | null; /** * If `false`, will skip normalizing the coordinates returned by `getPolygon`. * * **Note**: This prop is experimental * * @default true */ _normalize?: boolean; /** * Specifies the winding order of rings in the polygon data. * * **Note**: This prop is experimental * * @default 'CW' */ _windingOrder?: 'CW' | 'CCW'; /** * Material settings for lighting effect. Applies if `extruded: true`. * * @default true * @see https://deck.gl/docs/developer-guide/using-lighting */ material?: Material; }; const defaultLineColor: [number, number, number, number] = [0, 0, 0, 255]; const defaultFillColor: [number, number, number, number] = [0, 0, 0, 255]; const defaultProps: DefaultProps = { stroked: true, filled: true, extruded: false, elevationScale: 1, wireframe: false, _normalize: true, _windingOrder: 'CW', lineWidthUnits: 'meters', lineWidthScale: 1, lineWidthMinPixels: 0, lineWidthMaxPixels: Number.MAX_SAFE_INTEGER, lineJointRounded: false, lineMiterLimit: 4, getPolygon: {type: 'accessor', value: (f: any) => f.polygon}, // Polygon fill color getFillColor: {type: 'accessor', value: defaultFillColor}, // Point, line and polygon outline color getLineColor: {type: 'accessor', value: defaultLineColor}, // Line and polygon outline accessors getLineWidth: {type: 'accessor', value: 1}, // Polygon extrusion accessor getElevation: {type: 'accessor', value: 1000}, // Optional material for 'lighting' shader module material: true }; /** A composite layer that renders filled, stroked and/or extruded polygons. */ export default class PolygonLayer extends CompositeLayer< Required<_PolygonLayerProps> & ExtraProps > { static layerName = 'PolygonLayer'; static defaultProps = defaultProps; state!: { paths: number[][]; pathsDiff: | { startRow: number; endRow: number; }[] | null; }; initializeState(): void { this.state = { paths: [], pathsDiff: null }; if (this.props.getLineDashArray) { log.removed('getLineDashArray', 'PathStyleExtension')(); } } updateState({changeFlags}: UpdateParameters) { const geometryChanged = changeFlags.dataChanged || (changeFlags.updateTriggersChanged && (changeFlags.updateTriggersChanged.all || changeFlags.updateTriggersChanged.getPolygon)); if (geometryChanged && Array.isArray(changeFlags.dataChanged)) { const paths = this.state.paths.slice(); const pathsDiff = changeFlags.dataChanged.map(dataRange => replaceInRange({ data: paths, getIndex: p => p.__source.index, dataRange, replace: this._getPaths(dataRange) }) ); this.setState({paths, pathsDiff}); } else if (geometryChanged) { this.setState({ paths: this._getPaths(), pathsDiff: null }); } } private _getPaths(dataRange: {startRow?: number; endRow?: number} = {}): {path: number[]}[] { const {data, getPolygon, positionFormat, _normalize} = this.props; const paths: {path: number[]}[] = []; const positionSize = positionFormat === 'XY' ? 2 : 3; const {startRow, endRow} = dataRange; const {iterable, objectInfo} = createIterable(data, startRow, endRow); for (const object of iterable) { objectInfo.index++; let polygon = getPolygon(object, objectInfo); if (_normalize) { polygon = Polygon.normalize(polygon, positionSize); } const {holeIndices} = polygon; const positions = polygon.positions || polygon; if (holeIndices) { // split the positions array into `holeIndices.length + 1` rings // holeIndices[-1] falls back to 0 // holeIndices[holeIndices.length] falls back to positions.length for (let i = 0; i <= holeIndices.length; i++) { const path = positions.slice(holeIndices[i - 1] || 0, holeIndices[i] || positions.length); paths.push(this.getSubLayerRow({path}, object, objectInfo.index)); } } else { paths.push(this.getSubLayerRow({path: positions}, object, objectInfo.index)); } } return paths; } /* eslint-disable complexity */ renderLayers(): Layer | null | LayersList { // Layer composition props const { data, _dataDiff, stroked, filled, extruded, wireframe, _normalize, _windingOrder, elevationScale, transitions, positionFormat } = this.props; // Rendering props underlying layer const { lineWidthUnits, lineWidthScale, lineWidthMinPixels, lineWidthMaxPixels, lineJointRounded, lineMiterLimit, lineDashJustified } = this.props; // Accessor props for underlying layers const { getFillColor, getLineColor, getLineWidth, getLineDashArray, getElevation, getPolygon, updateTriggers, material } = this.props; const {paths, pathsDiff} = this.state; const FillLayer = this.getSubLayerClass('fill', SolidPolygonLayer); const StrokeLayer = this.getSubLayerClass('stroke', PathLayer); // Filled Polygon Layer const polygonLayer = this.shouldRenderSubLayer('fill', paths) && new FillLayer( { _dataDiff, extruded, elevationScale, filled, wireframe, _normalize, _windingOrder, getElevation, getFillColor, getLineColor: extruded && wireframe ? getLineColor : defaultLineColor, material, transitions }, this.getSubLayerProps({ id: 'fill', updateTriggers: updateTriggers && { getPolygon: updateTriggers.getPolygon, getElevation: updateTriggers.getElevation, getFillColor: updateTriggers.getFillColor, // using a legacy API to invalid lineColor attributes // if (extruded && wireframe) has changed lineColors: extruded && wireframe, getLineColor: updateTriggers.getLineColor } }), { data, positionFormat, getPolygon } ); // Polygon line layer const polygonLineLayer = !extruded && stroked && this.shouldRenderSubLayer('stroke', paths) && new StrokeLayer( { _dataDiff: pathsDiff && (() => pathsDiff), widthUnits: lineWidthUnits, widthScale: lineWidthScale, widthMinPixels: lineWidthMinPixels, widthMaxPixels: lineWidthMaxPixels, jointRounded: lineJointRounded, miterLimit: lineMiterLimit, dashJustified: lineDashJustified, // Already normalized _pathType: 'loop', transitions: transitions && { getWidth: transitions.getLineWidth, getColor: transitions.getLineColor, getPath: transitions.getPolygon }, getColor: this.getSubLayerAccessor(getLineColor), getWidth: this.getSubLayerAccessor(getLineWidth), getDashArray: this.getSubLayerAccessor(getLineDashArray) }, this.getSubLayerProps({ id: 'stroke', updateTriggers: updateTriggers && { getWidth: updateTriggers.getLineWidth, getColor: updateTriggers.getLineColor, getDashArray: updateTriggers.getLineDashArray } }), { data: paths, positionFormat, getPath: x => x.path } ); return [ // If not extruded: flat fill layer is drawn below outlines !extruded && polygonLayer, polygonLineLayer, // If extruded: draw fill layer last for correct blending behavior extruded && polygonLayer ]; } /* eslint-enable complexity */ }