diff --git a/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js b/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js index 769db79d..342fd0df 100644 --- a/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +++ b/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js @@ -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. @@ -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)) } @@ -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()) @@ -119,7 +116,6 @@ export default class MaplibreLayerAdapter { * @returns {Promise} */ 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) @@ -127,7 +123,7 @@ export default class MaplibreLayerAdapter { 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) } @@ -135,7 +131,7 @@ export default class MaplibreLayerAdapter { 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) } @@ -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) } @@ -168,7 +164,7 @@ export default class MaplibreLayerAdapter { * @param {Object} mapStyle */ addDataset (dataset, mapStyle) { - this._addLayers(dataset, mapStyle, this._pixelRatio) + this._addLayers(dataset, mapStyle) } /** @@ -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) @@ -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) } /** @@ -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) } diff --git a/providers/maplibre/src/maplibreProvider.js b/providers/maplibre/src/maplibreProvider.js index db4a96a6..f1d5dcf6 100755 --- a/providers/maplibre/src/maplibreProvider.js +++ b/providers/maplibre/src/maplibreProvider.js @@ -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' @@ -325,7 +324,7 @@ export default class MapLibreProvider { * @returns {Promise} */ 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) } @@ -341,7 +340,7 @@ export default class MapLibreProvider { * @returns {Promise} */ 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) } diff --git a/providers/maplibre/src/maplibreProvider.test.js b/providers/maplibre/src/maplibreProvider.test.js index 683dadd7..0da73629 100644 --- a/providers/maplibre/src/maplibreProvider.test.js +++ b/providers/maplibre/src/maplibreProvider.test.js @@ -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 }, @@ -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 () => { @@ -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', () => { diff --git a/providers/maplibre/src/utils/patternImages.js b/providers/maplibre/src/utils/patternImages.js index 686dfbb7..88279beb 100644 --- a/providers/maplibre/src/utils/patternImages.js +++ b/providers/maplibre/src/utils/patternImages.js @@ -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' @@ -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 = `` - // 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 = `${bgRect}${colored}` imageData = await rasteriseToImageData(svgString, physicalSize, physicalSize) @@ -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)) { diff --git a/providers/maplibre/src/utils/patternImages.test.js b/providers/maplibre/src/utils/patternImages.test.js index 534ef6d4..e10ce915 100644 --- a/providers/maplibre/src/utils/patternImages.test.js +++ b/providers/maplibre/src/utils/patternImages.test.js @@ -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() @@ -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 } ) }) @@ -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$/) }) }) diff --git a/src/services/patternRegistry.js b/src/services/patternRegistry.js index c90d4a99..d628f402 100644 --- a/src/services/patternRegistry.js +++ b/src/services/patternRegistry.js @@ -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 = { @@ -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` } } diff --git a/src/services/patternRegistry.test.js b/src/services/patternRegistry.test.js index 6a00fcdc..c9810e1a 100644 --- a/src/services/patternRegistry.test.js +++ b/src/services/patternRegistry.test.js @@ -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$/) }) @@ -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', () => { diff --git a/src/utils/patternUtils.js b/src/utils/patternUtils.js index 055ca1c4..9b50d8e1 100644 --- a/src/utils/patternUtils.js +++ b/src/utils/patternUtils.js @@ -1,7 +1,9 @@ // Border path rendered behind the pattern content in Key panel symbols (20×20 coordinate space). export const KEY_BORDER_PATH = '' // 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