diff --git a/docs/api-reference/layers/icon-layer.md b/docs/api-reference/layers/icon-layer.md index 462f2376705..26ac173f895 100644 --- a/docs/api-reference/layers/icon-layer.md +++ b/docs/api-reference/layers/icon-layer.md @@ -346,6 +346,19 @@ If you choose to use auto packing, this prop should be left empty. Icon size multiplier. + +#### `sizeBasis` (string, optional) {#sizebasis} + +- Default: 'height' + +Determines which dimension the size controls when scaling the icon. Valid values: + +'height': The icon size controls the height of the icon (default). + +'width': The icon size controls the width of the icon. + +This affects how the icon is scaled to maintain its aspect ratio based on the chosen size basis. + #### `sizeUnits` (string, optional) {#sizeunits} * Default: `pixels` @@ -436,7 +449,7 @@ Method called to retrieve the position of each object, returns `[lng, lat, z]`. - Default: `1` -The height of each object, in units specified by `sizeUnits` (default pixels). +The size of each object, in units specified by `sizeUnits` (default pixels). By default the size controls the height of the object, this can be changed with the sizeBasis property. - If a number is provided, it is used as the size for all objects. - If a function is provided, it is called on each object to retrieve its size. diff --git a/examples/layer-browser/src/examples/core-layers.js b/examples/layer-browser/src/examples/core-layers.js index 522f6d93d3a..53106bd6ff3 100644 --- a/examples/layer-browser/src/examples/core-layers.js +++ b/examples/layer-browser/src/examples/core-layers.js @@ -58,9 +58,16 @@ const ArcLayerExample = { const IconLayerExample = { layer: IconLayer, getData: () => dataSamples.points, + propTypes: { + sizeBasis: { + type: 'category', + value: ['height', 'width'] + } + }, props: { iconAtlas: 'data/icon-atlas.png', iconMapping: dataSamples.iconAtlas, + sizeBasis: 'height', sizeScale: 24, getPosition: d => d.COORDINATES, getColor: d => [64, 64, 72], diff --git a/modules/layers/src/icon-layer/icon-layer-uniforms.ts b/modules/layers/src/icon-layer/icon-layer-uniforms.ts index 82620f24fd4..9c794c4baf3 100644 --- a/modules/layers/src/icon-layer/icon-layer-uniforms.ts +++ b/modules/layers/src/icon-layer/icon-layer-uniforms.ts @@ -9,6 +9,7 @@ const uniformBlock = `\ uniform iconUniforms { float sizeScale; vec2 iconsTextureDim; + float sizeBasis; float sizeMinPixels; float sizeMaxPixels; bool billboard; @@ -24,6 +25,7 @@ type IconBindingProps = { type IconUniformProps = { sizeScale: number; iconsTextureDim: [number, number]; + sizeBasis: number; sizeMinPixels: number; sizeMaxPixels: number; billboard: boolean; @@ -40,6 +42,7 @@ export const iconUniforms = { uniformTypes: { sizeScale: 'f32', iconsTextureDim: 'vec2', + sizeBasis: 'f32', sizeMinPixels: 'f32', sizeMaxPixels: 'f32', billboard: 'f32', diff --git a/modules/layers/src/icon-layer/icon-layer-vertex.glsl.ts b/modules/layers/src/icon-layer/icon-layer-vertex.glsl.ts index 9146a336050..3653346754e 100644 --- a/modules/layers/src/icon-layer/icon-layer-vertex.glsl.ts +++ b/modules/layers/src/icon-layer/icon-layer-vertex.glsl.ts @@ -47,8 +47,9 @@ void main(void) { icon.sizeMinPixels, icon.sizeMaxPixels ); - // scale icon height to match instanceSize - float instanceScale = iconSize.y == 0.0 ? 0.0 : sizePixels / iconSize.y; + // Choose correct constraint based on the 'sizeBasis' value (0.0 = width, 1.0 = height) + float iconConstraint = icon.sizeBasis == 0.0 ? iconSize.x : iconSize.y; + float instanceScale = iconConstraint == 0.0 ? 0.0 : sizePixels / iconConstraint; // scale and rotate vertex in "pixel" value and convert back to fraction in clipspace vec2 pixelOffset = positions / 2.0 * iconSize + instanceOffsets; @@ -62,7 +63,6 @@ void main(void) { vec3 offset = vec3(pixelOffset, 0.0); DECKGL_FILTER_SIZE(offset, geometry); gl_Position.xy += project_pixel_size_to_clipspace(offset.xy); - } else { vec3 offset_common = vec3(project_pixel_size(pixelOffset), 0.0); DECKGL_FILTER_SIZE(offset_common, geometry); diff --git a/modules/layers/src/icon-layer/icon-layer.ts b/modules/layers/src/icon-layer/icon-layer.ts index eaf0553cda8..8bb5b33e15d 100644 --- a/modules/layers/src/icon-layer/icon-layer.ts +++ b/modules/layers/src/icon-layer/icon-layer.ts @@ -43,6 +43,10 @@ type _IconLayerProps = { * @default 'pixels' */ sizeUnits?: Unit; + /** + * The dimension to scale the image + */ + sizeBasis: 'height' | 'width'; /** * The minimum size in pixels. When using non-pixel `sizeUnits`, this prop can be used to prevent the icon from getting too small when zoomed out. */ @@ -105,6 +109,7 @@ const defaultProps: DefaultProps = { sizeScale: {type: 'number', value: 1, min: 0}, billboard: true, sizeUnits: 'pixels', + sizeBasis: 'height', sizeMinPixels: {type: 'number', min: 0, value: 0}, // min point radius in pixels sizeMaxPixels: {type: 'number', min: 0, value: Number.MAX_SAFE_INTEGER}, // max point radius in pixels alphaCutoff: {type: 'number', value: 0.05, min: 0, max: 1}, @@ -257,9 +262,9 @@ export default class IconLayer extends } draw({uniforms}): void { - const {sizeScale, sizeMinPixels, sizeMaxPixels, sizeUnits, billboard, alphaCutoff} = this.props; + const {sizeScale, sizeBasis, sizeMinPixels, sizeMaxPixels, sizeUnits, billboard, alphaCutoff} = + this.props; const {iconManager} = this.state; - const iconsTexture = iconManager.getTexture(); if (iconsTexture) { const model = this.state.model!; @@ -268,6 +273,7 @@ export default class IconLayer extends iconsTextureDim: [iconsTexture.width, iconsTexture.height], sizeUnits: UNIT[sizeUnits], sizeScale, + sizeBasis: sizeBasis === 'height' ? 1.0 : 0.0, sizeMinPixels, sizeMaxPixels, billboard, diff --git a/test/data/icons.png b/test/data/icons.png new file mode 100644 index 00000000000..a0ce87a70fe Binary files /dev/null and b/test/data/icons.png differ diff --git a/test/render/golden-images/icon-lnglat-rectangle.png b/test/render/golden-images/icon-lnglat-rectangle.png new file mode 100644 index 00000000000..7ce20a89c20 Binary files /dev/null and b/test/render/golden-images/icon-lnglat-rectangle.png differ diff --git a/test/render/test-cases/icon-layer.js b/test/render/test-cases/icon-layer.js index 0f0d3d37d59..b2b21ec7086 100644 --- a/test/render/test-cases/icon-layer.js +++ b/test/render/test-cases/icon-layer.js @@ -35,6 +35,48 @@ export default [ ], goldenImage: './test/render/golden-images/icon-lnglat.png' }, + { + name: 'icon-lnglat-rectangle', + viewState: { + longitude: -122.4269, + latitude: 37.75, + zoom: 15.6, + pitch: 0, + bearing: 0, + padding: { + top: 0, + bottom: 0, + left: 0, + right: 0 + } + }, + layers: [ + new IconLayer({ + id: 'icon-lnglat-multi', + data: [ + {position: [-122.4269, 37.7515], icon: 'tall'}, + {position: [-122.4269, 37.7505], icon: 'wide'}, + {position: [-122.4269, 37.7495], icon: 'square'}, + {position: [-122.4269, 37.7485], icon: 'short'} + ], + iconAtlas: './test/data/icons.png', + iconMapping: { + tall: {x: 0, y: 0, width: 40, height: 80, anchorY: 40}, + wide: {x: 40, y: 0, width: 80, height: 40, anchorY: 20}, + square: {x: 120, y: 0, width: 60, height: 60, anchorY: 30}, + short: {x: 180, y: 0, width: 60, height: 20, anchorY: 10} + }, + sizeUnits: 'pixels', + sizeScale: 1, + sizeBasis: 'width', + getPosition: d => d.position, + getIcon: d => d.icon, + getSize: 40, // target width in px + opacity: 0.8 + }) + ], + goldenImage: './test/render/golden-images/icon-lnglat-rectangle.png' + }, { name: 'icon-lnglat-external-buffer', viewState: {