Skip to content
27 changes: 11 additions & 16 deletions plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { addDatasetLayers, addSublayerLayers } from './layerBuilders.js'
import { getPatternConfigs, hasPattern } from './patternImages.js'
import { getSymbolConfigs } from './symbolImages.js'
import { mergeSublayer } from '../../utils/mergeSublayer.js'
import { scaleFactor } from '../../../../../../src/config/appConfig.js'

/**
* MapLibre GL JS implementation of the LayerAdapter interface for the datasets plugin.
Expand Down Expand Up @@ -45,13 +44,12 @@ export default class MaplibreLayerAdapter {
*/
async init (datasets, mapStyle) {
const mapStyleId = mapStyle.id
const pixelRatio = this._pixelRatio
await Promise.all([
this._mapProvider.addPatternsToMap(getPatternConfigs(datasets, this._patternRegistry), mapStyleId, this._patternRegistry),
this._mapProvider.addSymbolsToMap(getSymbolConfigs(datasets), mapStyle, this._symbolRegistry)
])
this._symbolLayerIds.clear()
datasets.forEach(dataset => this._addLayers(dataset, mapStyle, pixelRatio))
datasets.forEach(dataset => this._addLayers(dataset, mapStyle))
await new Promise(resolve => this._map.once('idle', resolve))
}

Expand Down Expand Up @@ -90,13 +88,12 @@ export default class MaplibreLayerAdapter {
await new Promise(resolve => this._map.once('idle', resolve))

const newStyleId = newMapStyle.id
const pixelRatio = this._pixelRatio
await Promise.all([
this._mapProvider.addPatternsToMap(getPatternConfigs(datasets, this._patternRegistry), newStyleId, this._patternRegistry),
this._mapProvider.addSymbolsToMap(getSymbolConfigs(datasets), newMapStyle, this._symbolRegistry)
])
this._symbolLayerIds.clear()
datasets.forEach(dataset => this._addLayers(dataset, newMapStyle, pixelRatio))
datasets.forEach(dataset => this._addLayers(dataset, newMapStyle))

// Re-push cached data for dynamic sources
dynamicSources.forEach(source => source.reapply())
Expand All @@ -119,23 +116,22 @@ export default class MaplibreLayerAdapter {
* @returns {Promise<void>}
*/
async onSizeChange (datasets, mapStyle) {
const pixelRatio = this._pixelRatio
await Promise.all([
this._mapProvider.addSymbolsToMap(getSymbolConfigs(datasets), mapStyle, this._symbolRegistry),
this._mapProvider.addPatternsToMap(getPatternConfigs(datasets, this._patternRegistry), mapStyle.id, this._patternRegistry)
])
datasets.forEach(dataset => {
getAllLayerIds(dataset).forEach(layerId => {
if (!this._symbolLayerIds.has(layerId) || !this._map.getLayer(layerId)) { return }
const imageId = this._symbolRegistry.getSymbolImageId(dataset, mapStyle, false, pixelRatio)
const imageId = this._symbolRegistry.getSymbolImageId(dataset, mapStyle, false, this._pixelRatio)
if (imageId) {
this._map.setLayoutProperty(layerId, 'icon-image', imageId)
}
})
if (hasPattern(dataset)) {
const { fillLayerId } = getLayerIds(dataset)
if (this._map.getLayer(fillLayerId)) {
const imageId = this._patternRegistry.getPatternImageId(dataset, mapStyle.id, pixelRatio)
const imageId = this._patternRegistry.getPatternImageId(dataset, mapStyle.id, this._pixelRatio)
if (imageId) {
this._map.setPaintProperty(fillLayerId, 'fill-pattern', imageId)
}
Expand All @@ -145,13 +141,13 @@ export default class MaplibreLayerAdapter {
const merged = mergeSublayer(dataset, sublayer)
const { symbolLayerId, fillLayerId } = getSublayerLayerIds(dataset.id, sublayer.id)
if (this._map.getLayer(symbolLayerId)) {
const imageId = this._symbolRegistry.getSymbolImageId(merged, mapStyle, false, pixelRatio)
const imageId = this._symbolRegistry.getSymbolImageId(merged, mapStyle, false, this._pixelRatio)
if (imageId) {
this._map.setLayoutProperty(symbolLayerId, 'icon-image', imageId)
}
}
if (hasPattern(merged) && this._map.getLayer(fillLayerId)) {
const imageId = this._patternRegistry.getPatternImageId(merged, mapStyle.id, pixelRatio)
const imageId = this._patternRegistry.getPatternImageId(merged, mapStyle.id, this._pixelRatio)
if (imageId) {
this._map.setPaintProperty(fillLayerId, 'fill-pattern', imageId)
}
Expand All @@ -168,7 +164,7 @@ export default class MaplibreLayerAdapter {
* @param {Object} mapStyle
*/
addDataset (dataset, mapStyle) {
this._addLayers(dataset, mapStyle, this._pixelRatio)
this._addLayers(dataset, mapStyle)
}

/**
Expand Down Expand Up @@ -271,7 +267,6 @@ export default class MaplibreLayerAdapter {
*/
async setStyle (dataset, mapStyle) {
const mapStyleId = mapStyle.id
const pixelRatio = this._pixelRatio
getAllLayerIds(dataset).forEach(layerId => {
if (this._map.getLayer(layerId)) {
this._map.removeLayer(layerId)
Expand All @@ -282,7 +277,7 @@ export default class MaplibreLayerAdapter {
this._mapProvider.addPatternsToMap(getPatternConfigs([dataset], this._patternRegistry), mapStyleId, this._patternRegistry),
this._mapProvider.addSymbolsToMap(getSymbolConfigs([dataset]), mapStyle, this._symbolRegistry)
])
this._addLayers(dataset, mapStyle, pixelRatio)
this._addLayers(dataset, mapStyle)
}

/**
Expand Down Expand Up @@ -364,11 +359,11 @@ export default class MaplibreLayerAdapter {
// ─── Private ─────────────────────────────────────────────────────────────────

get _pixelRatio () {
return this._mapProvider.map.getPixelRatio() * (scaleFactor[this._mapProvider.mapSize] || 1)
return this._mapProvider.map.getPixelRatio()
}

_addLayers (dataset, mapStyle, pixelRatio) {
const sourceId = addDatasetLayers(this._map, dataset, mapStyle, this._symbolRegistry, this._patternRegistry, pixelRatio)
_addLayers (dataset, mapStyle) {
const sourceId = addDatasetLayers(this._map, dataset, mapStyle, this._symbolRegistry, this._patternRegistry, this._pixelRatio)
this._datasetSourceMap.set(dataset.id, sourceId)
this._maintainSymbolOrdering(dataset)
}
Expand Down
5 changes: 2 additions & 3 deletions providers/maplibre/src/maplibreProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import { DEFAULTS, supportedShortcuts } from './defaults.js'
import { scaleFactor } from '../../../src/config/appConfig.js'
import { cleanCanvas, applyPreventDefaultFix } from './utils/maplibreFixes.js'
import { attachMapEvents } from './mapEvents.js'
import { attachAppEvents } from './appEvents.js'
Expand Down Expand Up @@ -325,7 +324,7 @@ export default class MapLibreProvider {
* @returns {Promise<void>}
*/
async addSymbolsToMap (symbolConfigs, mapStyle, symbolRegistry) {
const pixelRatio = (this.map.getPixelRatio() || 1) * (scaleFactor[this.mapSize] || 1)
const pixelRatio = this.map.getPixelRatio() || 1
return addSymbolsToMap(this.map, symbolConfigs, mapStyle, symbolRegistry, pixelRatio)
}

Expand All @@ -341,7 +340,7 @@ export default class MapLibreProvider {
* @returns {Promise<void>}
*/
async addPatternsToMap (patternConfigs, mapStyleId, patternRegistry) {
const pixelRatio = (this.map.getPixelRatio() || 1) * (scaleFactor[this.mapSize] || 1)
const pixelRatio = this.map.getPixelRatio() || 1
return addPatternsToMap(this.map, patternConfigs, mapStyleId, patternRegistry, pixelRatio)
}

Expand Down
26 changes: 8 additions & 18 deletions providers/maplibre/src/maplibreProvider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { addSymbolsToMap } from './utils/symbolImages.js'
import { addPatternsToMap } from './utils/patternImages.js'
import { getAreaDimensions, getCardinalMove, getResolution, getPaddedBounds, isGeometryObscured } from './utils/spatial.js'
import { symbolRegistry } from '../../../src/services/symbolRegistry.js'
// const addSymbolsToMapSpy = jest.spyOn(symbolRegistry, 'addSymbolsToMap').mockImplementation(() => Promise.resolve())

jest.mock('./defaults.js', () => ({
DEFAULTS: { animationDuration: 400, coordinatePrecision: 7 },
Expand Down Expand Up @@ -283,9 +282,9 @@ describe('MapLibreProvider', () => {
const p = makeProvider()
await doInitMap(p)
map.getPixelRatio.mockReturnValue(2)
p.mapSize = 'medium' // scaleFactor['medium'] = 1.5
p.mapSize = 'medium'
await p.addSymbolsToMap([], { id: 'test' }, symbolRegistry)
expect(addSymbolsToMap).toHaveBeenCalledWith(map, [], { id: 'test' }, symbolRegistry, 3)
expect(addSymbolsToMap).toHaveBeenCalledWith(map, [], { id: 'test' }, symbolRegistry, 2)
})

test('addSymbolsToMap falls back to pixelRatio 1 when getPixelRatio returns 0', async () => {
Expand All @@ -297,33 +296,24 @@ describe('MapLibreProvider', () => {
expect(addSymbolsToMap).toHaveBeenCalledWith(map, [], { id: 'test' }, symbolRegistry, 1)
})

test('addPatternsToMap delegates to utility with map instance and pixelRatio', async () => {
test('addPatternsToMap falls back to pixelRatio 1 when getPixelRatio returns 0', async () => {
const p = makeProvider()
await doInitMap(p)
map.getPixelRatio.mockReturnValue(0)
const configs = [{ fillPattern: 'dot' }]
const registry = {}
await p.addPatternsToMap(configs, 'test', registry)
expect(addPatternsToMap).toHaveBeenCalledWith(map, configs, 'test', registry, 1) // getPixelRatio()=1, mapSize unset → 1*1
expect(addPatternsToMap).toHaveBeenCalledWith(map, configs, 'test', registry, 1)
})

test('addPatternsToMap computes pixelRatio from getPixelRatio and mapSize scale factor', async () => {
test('addPatternsToMap is called with pixelRatio from getPixelRatio', async () => {
const p = makeProvider()
await doInitMap(p)
map.getPixelRatio.mockReturnValue(2)
p.mapSize = 'medium' // scaleFactor['medium'] = 1.5
const registry = {}
await p.addPatternsToMap([], 'test', registry)
expect(addPatternsToMap).toHaveBeenCalledWith(map, [], 'test', registry, 3) // 2 * 1.5
})

test('addPatternsToMap falls back to pixelRatio 1 when getPixelRatio returns 0', async () => {
const p = makeProvider()
await doInitMap(p)
map.getPixelRatio.mockReturnValue(0)
p.mapSize = 'small' // scaleFactor['small'] = 1
p.mapSize = 'medium'
const registry = {}
await p.addPatternsToMap([], 'test', registry)
expect(addPatternsToMap).toHaveBeenCalledWith(map, [], 'test', registry, 1) // (0 || 1) * 1
expect(addPatternsToMap).toHaveBeenCalledWith(map, [], 'test', registry, 2)
})

describe('setHoverCursor', () => {
Expand Down
8 changes: 3 additions & 5 deletions providers/maplibre/src/utils/patternImages.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { injectColors, PATTERN_MIN_PIXEL_RATIO } from '../../../../src/utils/patternUtils.js'
import { injectColors, getEffectivePixelRatio } from '../../../../src/utils/patternUtils.js'
import { getValueForStyle } from '../../../../src/utils/getValueForStyle.js'
import { rasteriseToImageData } from './rasteriseToImageData.js'

Expand Down Expand Up @@ -32,9 +32,7 @@ const rasterisePattern = async (dataset, mapStyleId, patternRegistry, pixelRatio
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
const colored = injectColors(innerContent, fg, bg)
const bgRect = `<rect width="16" height="16" fill="${bg}"/>`
// effectiveRatio floored at PATTERN_MIN_PIXEL_RATIO keeps the canvas at ≥16px physical so
// SVG detail renders crisply. Logical tile size = physicalSize / effectiveRatio = 8px always.
const effectiveRatio = Math.max(PATTERN_MIN_PIXEL_RATIO, pixelRatio)
const effectiveRatio = getEffectivePixelRatio(pixelRatio)
const physicalSize = Math.round(8 * effectiveRatio)
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${physicalSize}" height="${physicalSize}" viewBox="0 0 16 16">${bgRect}${colored}</svg>`
imageData = await rasteriseToImageData(svgString, physicalSize, physicalSize)
Expand Down Expand Up @@ -62,7 +60,7 @@ export const addPatternsToMap = async (map, styleArray, mapStyleId, patternRegis
return
}

const effectiveRatio = Math.max(PATTERN_MIN_PIXEL_RATIO, pixelRatio)
const effectiveRatio = getEffectivePixelRatio(pixelRatio)
await Promise.all(styleArray.map(async (config) => {
const imageId = patternRegistry.getPatternImageId(config, mapStyleId, pixelRatio)
if (!imageId || map.hasImage(imageId)) {
Expand Down
10 changes: 5 additions & 5 deletions providers/maplibre/src/utils/patternImages.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('addPatternsToMap', () => {

it('skips addImage when image is already registered', async () => {
const style = { fillPattern: 'stripes' }
const pixelRatio = 2
const pixelRatio = 1
const map = makeMap(['pattern-mpxwil-2x'])
await addPatternsToMap(map, [style], OUTDOOR, patternRegistry, pixelRatio)
expect(map.addImage).not.toHaveBeenCalled()
Expand Down Expand Up @@ -89,9 +89,9 @@ describe('addPatternsToMap', () => {
await addPatternsToMap(map, [config], OUTDOOR, patternRegistry, 2)
expect(map.addImage).toHaveBeenCalledTimes(1)
expect(map.addImage).toHaveBeenCalledWith(
expect.stringMatching(/^pattern-[a-z0-9]+-2x$/),
expect.stringMatching(/^pattern-[a-z0-9]+-4x$/),
expect.any(Object),
{ pixelRatio: 2 }
{ pixelRatio: 4 }
)
})

Expand All @@ -116,8 +116,8 @@ describe('addPatternsToMap', () => {
const [id2x] = map1.addImage.mock.calls[0]
const [id3x] = map2.addImage.mock.calls[0]
expect(id2x).not.toBe(id3x)
expect(id2x).toMatch(/-2x$/)
expect(id3x).toMatch(/-3x$/)
expect(id2x).toMatch(/-4x$/)
expect(id3x).toMatch(/-6x$/)
})
})

Expand Down
4 changes: 2 additions & 2 deletions src/services/patternRegistry.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BUILT_IN_PATTERNS } from '../config/patternConfig.js'
import { getValueForStyle } from '../utils/getValueForStyle.js'
import { KEY_BORDER_PATH, PATTERN_MIN_PIXEL_RATIO, injectColors, hashString } from '../utils/patternUtils.js'
import { KEY_BORDER_PATH, getEffectivePixelRatio, injectColors, hashString } from '../utils/patternUtils.js'
const patterns = new Map()

export const patternRegistry = {
Expand Down Expand Up @@ -97,7 +97,7 @@ export const patternRegistry = {
}
const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
const effectiveRatio = Math.max(PATTERN_MIN_PIXEL_RATIO, pixelRatio)
const effectiveRatio = getEffectivePixelRatio(pixelRatio)
return `pattern-${hashString(innerContent + fg + bg)}-${effectiveRatio}x`
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/services/patternRegistry.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -132,11 +132,9 @@ describe('patternRegistry', () => {
expect(idA).not.toBe(idB)
})

test('floors effective ratio at PATTERN_MIN_PIXEL_RATIO so low-DPI ids match 2x', () => {
test('floors effective ratio at 2 * so low-DPI ids match 2x', () => {
const dataset = { fillPattern: 'dot' }
const id1x = patternRegistry.getPatternImageId(dataset, 'style-a', 1)
const id2x = patternRegistry.getPatternImageId(dataset, 'style-a', 2)
expect(id1x).toBe(id2x)
expect(id1x).toMatch(/-2x$/)
})

Expand All @@ -145,8 +143,8 @@ describe('patternRegistry', () => {
const id2x = patternRegistry.getPatternImageId(dataset, 'style-a', 2)
const id3x = patternRegistry.getPatternImageId(dataset, 'style-a', 3)
expect(id2x).not.toBe(id3x)
expect(id2x).toMatch(/-2x$/)
expect(id3x).toMatch(/-3x$/)
expect(id2x).toMatch(/-4x$/)
expect(id3x).toMatch(/-6x$/)
})

test('falls back to "black" foreground and "transparent" background when colours are absent', () => {
Expand Down
4 changes: 3 additions & 1 deletion src/utils/patternUtils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Border path rendered behind the pattern content in Key panel symbols (20×20 coordinate space).
export const KEY_BORDER_PATH = '<path d="M19 2.862v14.275c0 1.028-.835 1.862-1.862 1.862H2.863c-1.028 0-1.862-.835-1.862-1.862V2.862C1.001 1.834 1.836 1 2.863 1h14.275C18.166 1 19 1.835 19 2.862z" fill="{{backgroundColor}}" stroke="{{foregroundColor}}" stroke-width="2"/>'
// Minimum oversampling — keeps 16×16 physical pixels as the floor so patterns remain crisp.
export const PATTERN_MIN_PIXEL_RATIO = 2
const PATTERN_MIN_PIXEL_RATIO = 2

export const getEffectivePixelRatio = (pixelRatio) => Math.max(PATTERN_MIN_PIXEL_RATIO, pixelRatio * 2)

export const hashString = (str) => {
let hash = 0
Expand Down
Loading