diff --git a/src/data/bucket/symbol_bucket.ts b/src/data/bucket/symbol_bucket.ts index f20f5838c1..f074074347 100644 --- a/src/data/bucket/symbol_bucket.ts +++ b/src/data/bucket/symbol_bucket.ts @@ -56,6 +56,8 @@ import type {SizeData} from '../../symbol/symbol_size'; import type {FeatureStates} from '../../source/source_state'; import type {ImagePosition} from '../../render/image_atlas'; import type {VectorTileLayer} from '@mapbox/vector-tile'; +import CanvasComparer from '../../util/canvas_comparer'; +import { markedStringToParts } from '../../util/util'; export type SingleCollisionBox = { x1: number; @@ -362,6 +364,8 @@ class SymbolBucket implements Bucket { allowVerticalPlacement: boolean; hasRTLText: boolean; + textCache: {[key: string]: string[]}; + constructor(options: BucketParameters) { this.collisionBoxArray = options.collisionBoxArray; this.zoom = options.zoom; @@ -404,6 +408,8 @@ class SymbolBucket implements Bucket { this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); this.sourceID = options.sourceID; + + this.textCache = {}; } createArrays() { @@ -415,15 +421,52 @@ class SymbolBucket implements Bucket { this.symbolInstances = new SymbolInstanceArray(); } - calculateGlyphDependencies(text: string, stack: {[_: number]: boolean}, textAlongLine: boolean, allowVerticalPlacement: boolean, doesAllowVerticalWritingMode: boolean) { - for (let i = 0; i < text.length; i++) { - stack[text.charCodeAt(i)] = true; - if ((textAlongLine || allowVerticalPlacement) && doesAllowVerticalWritingMode) { - const verticalChar = verticalizedCharacterMap[text.charAt(i)]; - if (verticalChar) { - stack[verticalChar.charCodeAt(0)] = true; - } - } + calculateGlyphDependencies(text: string, stack: {[_: string]: boolean}, textAlongLine: boolean, allowVerticalPlacement: boolean, doesAllowVerticalWritingMode: boolean) { + // let graphemes = []; + + // if (text in this.textCache) { + // // console.log('symbol_bucket cache hit', text); + // graphemes = this.textCache[text]; + + // } else { + // // console.log('symbol bucket cache miss', text); + + // // const segmenter = new Intl.Segmenter( + // // 'en', {granularity: 'grapheme'} + // // ); + // // graphemes = Array.from(segmenter.segment(text), s => s.segment); + + // const graphemes = [...text]; + + // // console.log('shaping', line.text); + + // // const canvasComparer = new CanvasComparer(); + + // // if (!canvasComparer.isLatin(text) && !canvasComparer.compareCanvases(text, graphemes)) { + // // canvasComparer.mergeStrings(graphemes); + // // } + + // this.textCache[text] = [...graphemes]; + // } + + + // const graphemes = [...text]; + + const graphemes = markedStringToParts(text); + + for (let i = 0; i < graphemes.length; i++) { + // OLIVER + // stack[text.charCodeAt(i).toString()] = true; + stack[graphemes[i]] = true; + // if ((textAlongLine || allowVerticalPlacement) && doesAllowVerticalWritingMode) { + // // OLIVER + // const verticalChar = verticalizedCharacterMap[text.charAt(i)]; + // if (verticalChar) { + // // OLIVER + // // stack[verticalChar.charCodeAt(0).toString()] = true; + // stack[verticalChar[i]] = true; + // } + // } } } diff --git a/src/render/glyph_atlas.ts b/src/render/glyph_atlas.ts index a0afc79641..93315756df 100644 --- a/src/render/glyph_atlas.ts +++ b/src/render/glyph_atlas.ts @@ -36,12 +36,14 @@ export default class GlyphAtlas { const positions = {}; const bins = []; + // console.log('glyph_atlas', stacks); for (const stack in stacks) { const glyphs = stacks[stack]; const stackPositions = positions[stack] = {}; for (const id in glyphs) { - const src = glyphs[+id]; + //const src = glyphs[+id]; + const src = glyphs[id]; if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue; const bin = { @@ -62,7 +64,8 @@ export default class GlyphAtlas { const glyphs = stacks[stack]; for (const id in glyphs) { - const src = glyphs[+id]; + // const src = glyphs[+id]; + const src = glyphs[id]; if (!src || src.bitmap.width === 0 || src.bitmap.height === 0) continue; const bin = positions[stack][id].rect; AlphaImage.copy(src.bitmap, image, {x: 0, y: 0}, {x: bin.x + padding, y: bin.y + padding}, src.bitmap); diff --git a/src/render/glyph_manager.ts b/src/render/glyph_manager.ts index 4e927aab48..3679f2b125 100644 --- a/src/render/glyph_manager.ts +++ b/src/render/glyph_manager.ts @@ -83,6 +83,9 @@ export default class GlyphManager { } glyph = this._tinySDF(entry, stack, id); + + // console.log('glyph_manager', glyph); + if (glyph) { entry.glyphs[id] = glyph; callback(null, {stack, id, glyph}); @@ -161,6 +164,7 @@ export default class GlyphManager { } _doesCharSupportLocalGlyph(id: number): boolean { + return true; /* eslint-disable new-cap */ return !!this.localIdeographFontFamily && (isChar['CJK Unified Ideographs'](id) || @@ -200,7 +204,27 @@ export default class GlyphManager { }); } - const char = tinySDF.draw(String.fromCharCode(id)); + console.log('tinysdf', id, typeof id); + // OLIVER + // const char = tinySDF.draw(String.fromCharCode(id)); + + // let char; + + // const cachedChar = localStorage.getItem(id as any); + + // if (cachedChar) { + // console.log('cached'); + // char = JSON.parse(cachedChar); + // char.data = Object.values(char.data); + // } else { + // console.log('not cached'); + // char = tinySDF.draw(id as any); + // localStorage.setItem(id as any, JSON.stringify(char)); + // } + + const char = tinySDF.draw(id as any); + + // console.log('char', char, JSON.stringify(char)); /** * TinySDF's "top" is the distance from the alphabetic baseline to the top of the glyph. diff --git a/src/source/worker_tile.ts b/src/source/worker_tile.ts index 3675a97a64..35d3629968 100644 --- a/src/source/worker_tile.ts +++ b/src/source/worker_tile.ts @@ -138,7 +138,7 @@ class WorkerTile { let iconMap: {[_: string]: StyleImage}; let patternMap: {[_: string]: StyleImage}; - const stacks = mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(Number)); + const stacks = mapObject(options.glyphDependencies, (glyphs) => Object.keys(glyphs).map(String)); if (Object.keys(stacks).length) { actor.send('getGlyphs', {uid: this.uid, stacks, source: this.source, tileID: this.tileID, type: 'glyphs'}, (err, result) => { if (!error) { diff --git a/src/symbol/shaping.ts b/src/symbol/shaping.ts index dc87f31522..a4fdd9220b 100644 --- a/src/symbol/shaping.ts +++ b/src/symbol/shaping.ts @@ -6,7 +6,7 @@ import { import verticalizePunctuation from '../util/verticalize_punctuation'; import {plugin as rtlTextPlugin} from '../source/rtl_text_plugin'; import ONE_EM from './one_em'; -import {warnOnce} from '../util/util'; +import {markedStringToParts, warnOnce} from '../util/util'; import type {StyleGlyph, GlyphMetrics} from '../style/style_glyph'; import {GLYPH_PBF_BORDER} from '../style/parse_glyph_pbf'; @@ -14,6 +14,7 @@ import type {ImagePosition} from '../render/image_atlas'; import {IMAGE_PADDING} from '../render/image_atlas'; import type {Rect, GlyphPosition} from '../render/glyph_atlas'; import {Formatted, FormattedSection} from '@maplibre/maplibre-gl-style-spec'; +import CanvasComparer from '../util/canvas_comparer'; enum WritingMode { none = 0, @@ -140,8 +141,10 @@ class TaggedString { return this.sectionIndex[index]; } - getCharCode(index: number): number { - return this.text.charCodeAt(index); + getCharCode(index: number): string { + // OLIVER + // return this.text.charCodeAt(index); + return this.text[index]; } verticalizePunctuation() { @@ -151,12 +154,14 @@ class TaggedString { trim() { let beginningWhitespace = 0; for (let i = 0; + // OLIVER i < this.text.length && whitespace[this.text.charCodeAt(i)]; i++) { beginningWhitespace++; } let trailingWhitespace = this.text.length; for (let i = this.text.length - 1; + // OLIVER i >= 0 && i >= beginningWhitespace && whitespace[this.text.charCodeAt(i)]; i--) { trailingWhitespace--; @@ -203,6 +208,7 @@ class TaggedString { return; } + // OLIVER this.text += String.fromCharCode(nextImageSectionCharCode); this.sections.push(SectionOptions.forImage(imageName)); this.sectionIndex.push(this.sections.length - 1); @@ -316,6 +322,7 @@ function shapeText( verticalizable: false }; + // console.log('shaping', 'glyphMap', glyphMap); shapeLines(shaping, glyphMap, glyphPositions, imagePositions, lines, lineHeight, textAnchor, textJustify, writingMode, spacing, allowVerticalPlacement, layoutTextSizeThisZoom); if (isEmpty(positionedLines)) return false; @@ -337,8 +344,9 @@ const whitespace: { }; const breakable: { - [_: number]: boolean; + [_: number | string]: boolean; } = { + ['\n']: true, [0x0a]: true, // newline [0x20]: true, // space [0x26]: true, // ampersand @@ -382,6 +390,7 @@ function getGlyphAdvance( } } +// safe to ignore this fucntion function determineAverageLineWidth(logicalInput: TaggedString, spacing: number, maxWidth: number, @@ -396,7 +405,7 @@ function determineAverageLineWidth(logicalInput: TaggedString, for (let index = 0; index < logicalInput.length(); index++) { const section = logicalInput.getSection(index); - totalWidth += getGlyphAdvance(logicalInput.getCharCode(index), section, glyphMap, imagePositions, spacing, layoutTextSize); + totalWidth += getGlyphAdvance(logicalInput.getCharCode(index) as any, section, glyphMap, imagePositions, spacing, layoutTextSize); } const lineCount = Math.max(1, Math.ceil(totalWidth / maxWidth)); @@ -505,6 +514,7 @@ function determineLineBreaks( symbolPlacement: string, layoutTextSize: number ): Array { + if (symbolPlacement !== 'point') return []; @@ -521,12 +531,12 @@ function determineLineBreaks( for (let i = 0; i < logicalInput.length(); i++) { const section = logicalInput.getSection(i); const codePoint = logicalInput.getCharCode(i); - if (!whitespace[codePoint]) currentX += getGlyphAdvance(codePoint, section, glyphMap, imagePositions, spacing, layoutTextSize); + if (!whitespace[codePoint]) currentX += getGlyphAdvance(codePoint as any, section, glyphMap, imagePositions, spacing, layoutTextSize); // Ideographic characters, spaces, and word-breaking punctuation that often appear without // surrounding spaces. if ((i < logicalInput.length() - 1)) { - const ideographicBreak = charAllowsIdeographicBreaking(codePoint); + const ideographicBreak = charAllowsIdeographicBreaking(codePoint as any); if (breakable[codePoint] || ideographicBreak || section.imageName) { potentialLineBreaks.push( @@ -535,7 +545,7 @@ function determineLineBreaks( currentX, targetWidth, potentialLineBreaks, - calculatePenalty(codePoint, logicalInput.getCharCode(i + 1), ideographicBreak && hasServerSuggestedBreakpoints), + calculatePenalty(codePoint as any, logicalInput.getCharCode(i + 1) as any, ideographicBreak && hasServerSuggestedBreakpoints), false)); } } @@ -583,6 +593,8 @@ function getAnchorAlignment(anchor: SymbolAnchor) { return {horizontalAlign, verticalAlign}; } +const textCache: {[key: string]: string[]} = {}; + function shapeLines(shaping: Shaping, glyphMap: { [_: string]: { @@ -615,6 +627,9 @@ function shapeLines(shaping: Shaping, textJustify === 'left' ? 0 : 0.5; let lineIndex = 0; + + //const canvasComparer = new CanvasComparer(); + for (const line of lines) { line.trim(); @@ -631,21 +646,54 @@ function shapeLines(shaping: Shaping, continue; } - for (let i = 0; i < line.length(); i++) { - const section = line.getSection(i); - const sectionIndex = line.getSectionIndex(i); - const codePoint = line.getCharCode(i); + // let graphemes = []; + + // if (line.text in textCache) { + // // console.log('cache hit', line.text); + // graphemes = textCache[line.text]; + + // } else { + // // console.log('cache miss', line.text); + + // // const segmenter = new Intl.Segmenter( + // // 'en', {granularity: 'grapheme'} + // // ); + // // graphemes = Array.from(segmenter.segment(line.text), s => s.segment); + + // const graphemes = [...line.text]; + + // // console.log('shaping', line.text); + + // // if (!canvasComparer.isLatin(line.text) && !canvasComparer.compareCanvases(line.text, graphemes)) { + // // canvasComparer.mergeStrings(graphemes); + // // } + + // textCache[line.text] = [...graphemes]; + // } + + // const graphemes = [...line.text]; + + const graphemes = markedStringToParts(line.text); + + for (let i = 0; i < graphemes.length; i++) { + // const section = line.getSection(i); + const section = line.getSection(0); + // const sectionIndex = line.getSectionIndex(i); + const sectionIndex = line.getSectionIndex(0); + // const codePoint = line.getCharCode(i) as any; + const codePoint = graphemes[i] as any; let baselineOffset = 0.0; let metrics = null; let rect = null; let imageName = null; let verticalAdvance = ONE_EM; - const vertical = !(writingMode === WritingMode.horizontal || - // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled. - (!allowVerticalPlacement && !charHasUprightVerticalOrientation(codePoint)) || - // If vertical placement is enabled, don't verticalize glyphs that - // are from complex text layout script, or whitespaces. - (allowVerticalPlacement && (whitespace[codePoint] || charInComplexShapingScript(codePoint)))); + // const vertical = !(writingMode === WritingMode.horizontal || + // // Don't verticalize glyphs that have no upright orientation if vertical placement is disabled. + // (!allowVerticalPlacement && !charHasUprightVerticalOrientation(codePoint)) || + // // If vertical placement is enabled, don't verticalize glyphs that + // // are from complex text layout script, or whitespaces. + // (allowVerticalPlacement && (whitespace[codePoint] || charInComplexShapingScript(codePoint)))); + const vertical = false; if (!section.imageName) { const positions = glyphPositions[section.fontStack]; @@ -714,6 +762,8 @@ function shapeLines(shaping: Shaping, justifyLine(positionedGlyphs, 0, positionedGlyphs.length - 1, justify, lineOffset); } + // console.log('shaping', positionedGlyphs); + x = 0; const currentLineHeight = lineHeight * lineMaxScale + lineOffset; positionedLine.lineOffset = Math.max(lineOffset, maxLineOffset); diff --git a/src/util/canvas_comparer.ts b/src/util/canvas_comparer.ts new file mode 100644 index 0000000000..8944d74863 --- /dev/null +++ b/src/util/canvas_comparer.ts @@ -0,0 +1,69 @@ +export default class CanvasComparer { + private static canvas1: OffscreenCanvas = new OffscreenCanvas(100, 8); + private static canvas2: OffscreenCanvas = new OffscreenCanvas(100, 8); + private static ctx1: any = CanvasComparer.canvas1.getContext('2d', { willReadFrequently: true }); + private static ctx2: any = CanvasComparer.canvas2.getContext('2d', { willReadFrequently: true }); + + constructor() { + // this.canvas1 = new OffscreenCanvas(50, 15); + // this.canvas2 = new OffscreenCanvas(50, 15); + // this.ctx1 = this.canvas1.getContext('2d', { willReadFrequently: true }); + // this.ctx2 = this.canvas2.getContext('2d', { willReadFrequently: true }); + + CanvasComparer.ctx1.font = '3px Arial'; + CanvasComparer.ctx2.font = '3px Arial'; + } + + public compareCanvases(total: string, parts: string[]): boolean { + CanvasComparer.ctx1.clearRect(0, 0, CanvasComparer.canvas1.width, CanvasComparer.canvas1.height); + CanvasComparer.ctx2.clearRect(0, 0, CanvasComparer.canvas2.width, CanvasComparer.canvas2.height); + + CanvasComparer.ctx1.fillText(total, 0, 5); + const offset1 = CanvasComparer.ctx1.measureText(total).width; + + let offset2 = 0; + for (const part of parts) { + CanvasComparer.ctx2.fillText(part, offset2, 5); + offset2 += CanvasComparer.ctx2.measureText(part).width; + } + + if (offset1 !== offset2) { + return false; + } + + if (offset1 === 0) { + return false; + } + + const imageData1 = CanvasComparer.ctx1.getImageData(0, 0, offset2, 8); + const imageData2 = CanvasComparer.ctx2.getImageData(0, 0, offset2, 8); + + if (imageData1.data.length !== imageData2.data.length) { + return false; + } else { + for (let i = 0; i < imageData1.data.length; i++) { + if (imageData1.data[i] !== imageData2.data[i]) { + return false; + } + } + return true; + } + } + + public mergeStrings(parts: string[]): string[] { + let i = 0; + while (i < parts.length - 1) { + if (!this.compareCanvases(`${parts[i]}${parts[i + 1]}`, [parts[i], parts[i + 1]])) { + parts.splice(i, 2, parts[i] + parts[i + 1]); + //i = 0; // commenting out this might require grapheme clusters in the first place + continue; + } + i++; + } + return parts; + } + + public isLatin(text) { + return /^[a-zA-Z\u00C0-\u024F\u1E00-\u1EFF\s\-'",.;:()!?]+$/u.test(text); + } +} diff --git a/src/util/script_detection.ts b/src/util/script_detection.ts index a1c143f9b2..25faf19765 100644 --- a/src/util/script_detection.ts +++ b/src/util/script_detection.ts @@ -4,6 +4,7 @@ import isChar from './is_char_in_unicode_block'; export function allowsIdeographicBreaking(chars: string) { for (const char of chars) { + // OLIVER if (!charAllowsIdeographicBreaking(char.charCodeAt(0))) return false; } return true; @@ -11,6 +12,7 @@ export function allowsIdeographicBreaking(chars: string) { export function allowsVerticalWritingMode(chars: string) { for (const char of chars) { + // OLIVER if (charHasUprightVerticalOrientation(char.charCodeAt(0))) return true; } return false; @@ -18,6 +20,7 @@ export function allowsVerticalWritingMode(chars: string) { export function allowsLetterSpacing(chars: string) { for (const char of chars) { + // OLIVER if (!charAllowsLetterSpacing(char.charCodeAt(0))) return false; } return true; @@ -309,6 +312,7 @@ export function charInSupportedScript(char: number, canRenderRTL: boolean) { export function stringContainsRTLText(chars: string): boolean { for (const char of chars) { + // OLIVER if (charInRTLScript(char.charCodeAt(0))) { return true; } @@ -318,6 +322,7 @@ export function stringContainsRTLText(chars: string): boolean { export function isStringInSupportedScript(chars: string, canRenderRTL: boolean) { for (const char of chars) { + // OLIVER if (!charInSupportedScript(char.charCodeAt(0), canRenderRTL)) { return false; } diff --git a/src/util/util.ts b/src/util/util.ts index dc7d3aed53..e8604cef5a 100644 --- a/src/util/util.ts +++ b/src/util/util.ts @@ -517,6 +517,7 @@ export function b64EncodeUnicode(str: string) { return btoa( encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (match, p1) => { + // OLIVER return String.fromCharCode(Number('0x' + p1)); //eslint-disable-line } ) @@ -526,6 +527,7 @@ export function b64EncodeUnicode(str: string) { // Unicode compliant decoder for base64-encoded strings export function b64DecodeUnicode(str: string) { return decodeURIComponent(atob(str).split('').map((c) => { + // OLIVER return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); //eslint-disable-line }).join('')); } @@ -580,3 +582,25 @@ export function arrayBufferToImage(data: ArrayBuffer, callback: (err?: Error | n const blob: Blob = new Blob([new Uint8Array(data)], {type: 'image/png'}); img.src = data.byteLength ? URL.createObjectURL(blob) : transparentPngUrl; } + +export function markedStringToParts(markedString) { + const result = []; + const customJoiningCharacter = '@'; + if (markedString.length === 0 || markedString[0] === customJoiningCharacter) { + return result; + } + let currentPart = markedString[0]; + for (let i = 1; i < markedString.length; ++i) { + if (markedString[i] === customJoiningCharacter) { + continue; + } + if (markedString[i - 1] === customJoiningCharacter) { + currentPart += markedString[i]; + } else { + result.push(currentPart); + currentPart = markedString[i]; + } + } + result.push(currentPart); + return result; +} diff --git a/src/util/verticalize_punctuation.ts b/src/util/verticalize_punctuation.ts index f7d05d69c1..9dc48416f9 100644 --- a/src/util/verticalize_punctuation.ts +++ b/src/util/verticalize_punctuation.ts @@ -90,7 +90,9 @@ export default function verticalizePunctuation(input: string) { let output = ''; for (let i = 0; i < input.length; i++) { + // OLIVER const nextCharCode = input.charCodeAt(i + 1) || null; + // OLIVER const prevCharCode = input.charCodeAt(i - 1) || null; const canReplacePunctuation = ( diff --git a/test/debug-pages/debug2.html b/test/debug-pages/debug2.html new file mode 100644 index 0000000000..f98c07ea89 --- /dev/null +++ b/test/debug-pages/debug2.html @@ -0,0 +1,149 @@ + + + + + Index by Graheme + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/test/debug-pages/style2.json b/test/debug-pages/style2.json new file mode 100644 index 0000000000..a46c6f7449 --- /dev/null +++ b/test/debug-pages/style2.json @@ -0,0 +1,573 @@ +{ + "version": 8, + "name": "SwissMap style", + "sources": { + "swissmap": { + "type": "vector", + "url": "https://wipfli.github.io/swiss-map/tilejson-swissmap.json" + }, + "qrank": { + "type": "vector", + "url": "https://wipfli.github.io/swiss-map/tilejson-qrank.json" + }, + "roadnames": { + "type": "vector", + "url": "https://wipfli.github.io/swiss-map/tilejson-roadnames.json" + }, + "landcover": { + "type": "vector", + "url": "https://wipfli.github.io/swiss-map/tilejson-h3-landcover.json" + } + }, + "light": { + "intensity": 0.1 + }, + "glyphs": "https://wipfli.github.io/swiss-map/font/{fontstack}/{range}.pbf", + "layers": [ + { + "id": "background", + "type": "background", + "layout": { + "visibility": "visible" + }, + "paint": { + "background-color": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + "#a8dab5", + 11, + "#f8f9fa" + ] + } + }, + { + "id": "Grass-Bare-Snow", + "type": "fill", + "source": "landcover", + "source-layer": "landcover", + "filter": [ + "==", + [ + "get", + "kind" + ], + "Grass-Bare-Snow" + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#b6dcc1", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1, + 11, + 0 + ] + } + }, + { + "id": "Bare-Snow", + "type": "fill", + "source": "landcover", + "source-layer": "landcover", + "filter": [ + "==", + [ + "get", + "kind" + ], + "Bare-Snow" + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#f3ede0", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1, + 11, + 0 + ] + } + }, + { + "id": "Snow", + "type": "fill", + "source": "landcover", + "source-layer": "landcover", + "filter": [ + "==", + [ + "get", + "kind" + ], + "Snow" + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "white", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1, + 11, + 0 + ] + } + }, + { + "id": "Crops", + "type": "fill", + "source": "landcover", + "source-layer": "landcover", + "filter": [ + "==", + [ + "get", + "kind" + ], + "Crops" + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#bbe2c6", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1, + 11, + 0 + ] + } + }, + { + "id": "Tree", + "type": "fill", + "source": "landcover", + "source-layer": "landcover", + "filter": [ + "==", + [ + "get", + "kind" + ], + "Tree" + ], + "paint": { + "fill-color": "#94d2a5", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1, + 11, + 0 + ] + } + }, + { + "id": "wood", + "type": "fill", + "source": "swissmap", + "source-layer": "wood", + "paint": { + "fill-color": "#ceead6", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 0, + 11, + 1 + ] + } + }, + { + "id": "BuiltUp", + "type": "fill", + "source": "landcover", + "source-layer": "landcover", + "filter": [ + "==", + [ + "get", + "kind" + ], + "BuiltUp" + ], + "paint": { + "fill-color": "#e8eaed", + "fill-opacity": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + 1, + 11, + 0 + ] + } + }, + { + "id": "residential", + "type": "fill", + "source": "swissmap", + "source-layer": "residential", + "minzoom": 10, + "maxzoom": 15, + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": { + "stops": [ + [ + 12, + "#e8eaed" + ], + [ + 14, + "#F1F2F4" + ] + ] + } + } + }, + { + "id": "waterway", + "type": "line", + "source": "swissmap", + "source-layer": "waterway", + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "#ABCDFB", + "line-width": { + "stops": [ + [ + 9, + 1 + ], + [ + 11, + 2 + ] + ] + } + } + }, + { + "id": "water", + "type": "fill", + "source": "swissmap", + "source-layer": "water", + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 10, + "#8ab4f8", + 11, + "#ABCDFB" + ] + } + }, + { + "id": "highway-path", + "type": "line", + "source": "swissmap", + "source-layer": "highway-path", + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#bdc3c7", + "line-dasharray": [ + 0.0, + 2.0 + ], + "line-width": { + "stops": [ + [ + 12, + 0 + ], + [ + 14, + 2 + ], + [ + 20, + 3 + ] + ] + } + } + }, + { + "id": "highway-footway", + "type": "line", + "source": "swissmap", + "source-layer": "highway-footway", + "minzoom": 15, + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible" + }, + "paint": { + "line-color": "#bdc3c7", + "line-dasharray": [ + 0.0, + 2.0 + ], + "line-width": { + "stops": [ + [ + 14, + 2 + ], + [ + 20, + 3 + ] + ] + } + } + }, + { + "id": "highway-tracktype-2", + "type": "line", + "source": "swissmap", + "source-layer": "highway-tracktype-2", + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "#bdc3c7", + "line-width": { + "stops": [ + [ + 12, + 0 + ], + [ + 14, + 1.5 + ] + ] + } + } + }, + { + "id": "highway-tracktype-3-4-5", + "type": "line", + "source": "swissmap", + "source-layer": "highway-tracktype-3-4-5", + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "#bdc3c7", + "line-dasharray": [ + 10.0, + 2.0 + ], + "line-width": { + "stops": [ + [ + 12, + 0 + ], + [ + 14, + 1.5 + ], + [ + 20, + 3 + ] + ] + } + } + }, + { + "id": "highway", + "type": "line", + "source": "swissmap", + "source-layer": "highway", + "layout": { + "line-cap": "butt", + "line-join": "round", + "visibility": "visible", + "line-sort-key": [ + "to-number", + [ + "get", + "line-sort-key" + ] + ] + }, + "paint": { + "line-color": [ + "get", + "line-color" + ], + "line-width": [ + "interpolate", + [ + "linear" + ], + [ + "zoom" + ], + 14, + [ + "to-number", + [ + "get", + "line-width" + ] + ], + 20, + [ + "to-number", + [ + "get", + "line-width-z20" + ] + ] + ] + } + }, + { + "id": "boundary", + "type": "line", + "source": "swissmap", + "source-layer": "boundary", + "layout": { + "visibility": "visible" + }, + "paint": { + "line-width": 2, + "line-color": "#7f8c8d" + } + }, + { + "id": "building", + "type": "fill", + "source": "swissmap", + "minzoom": 15, + "source-layer": "building", + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-color": "#f1f3f4" + } + }, + { + "id": "building_outline", + "type": "line", + "source": "swissmap", + "source-layer": "building", + "minzoom": 15, + "layout": { + "visibility": "visible" + }, + "paint": { + "line-color": "#dee0e4", + "line-width": 1 + } + }, + { + "id": "building-extrusion", + "type": "fill-extrusion", + "source": "swissmap", + "minzoom": 15, + "source-layer": "building", + "filter": [ + "has", + "est_height" + ], + "layout": { + "visibility": "visible" + }, + "paint": { + "fill-extrusion-color": "#e6e7e9", + "fill-extrusion-height": [ + "to-number", + [ + "get", + "est_height" + ] + ], + "fill-extrusion-opacity": 1.0 + } + } + ], + "id": "swissmap-style" +} \ No newline at end of file