import {eachSource, eachLayer, eachProperty} from '../visit'; import type {LayerSpecification, StyleSpecification} from '../types.g'; function eachLayout(layer: LayerSpecification, callback: (_: LayerSpecification['layout'], __: string) => void) { for (const k in layer) { if (k.indexOf('layout') === 0) { callback(layer[k], k); } } } function eachPaint(layer: LayerSpecification, callback: (_: LayerSpecification['paint'], __: string) => void) { for (const k in layer) { if (k.indexOf('paint') === 0) { callback(layer[k], k); } } } function resolveConstant(style: StyleSpecification, value: any) { if (typeof value === 'string' && value[0] === '@') { return resolveConstant(style, (style as any).constants[value]); } else { return value; } } function isFunction(value) { return Array.isArray(value.stops); } function renameProperty(obj: Object, from: string, to: string) { obj[to] = obj[from]; delete obj[from]; } export default function migrateV8(style: StyleSpecification) { style.version = 8; // Rename properties, reverse coordinates in source and layers eachSource(style, (source) => { if (source.type === 'video' && source['url'] !== undefined) { renameProperty(source, 'url', 'urls'); } if (source.type === 'video') { source.coordinates.forEach((coord) => { return coord.reverse(); }); } }); eachLayer(style, (layer) => { eachLayout(layer, (layout) => { if (layout['symbol-min-distance'] !== undefined) { renameProperty(layout, 'symbol-min-distance', 'symbol-spacing'); } }); eachPaint(layer, (paint) => { if (paint['background-image'] !== undefined) { renameProperty(paint, 'background-image', 'background-pattern'); } if (paint['line-image'] !== undefined) { renameProperty(paint, 'line-image', 'line-pattern'); } if (paint['fill-image'] !== undefined) { renameProperty(paint, 'fill-image', 'fill-pattern'); } }); }); // Inline Constants eachProperty(style, {paint: true, layout: true}, (property) => { const value = resolveConstant(style, property.value); if (isFunction(value)) { value.stops.forEach((stop) => { stop[1] = resolveConstant(style, stop[1]); }); } property.set(value); }); delete style['constants']; eachLayer(style, (layer) => { // get rid of text-max-size, icon-max-size // turn text-size, icon-size into layout properties // https://github.com/mapbox/mapbox-gl-style-spec/issues/255 eachLayout(layer, (layout) => { delete layout['text-max-size']; delete layout['icon-max-size']; }); eachPaint(layer, (paint) => { if (paint['text-size']) { if (!layer.layout) layer.layout = {}; layer.layout['text-size'] = paint['text-size']; delete paint['text-size']; } if (paint['icon-size']) { if (!layer.layout) layer.layout = {}; layer.layout['icon-size'] = paint['icon-size']; delete paint['icon-size']; } }); }); function migrateFontStack(font) { function splitAndTrim(string) { return string.split(',').map((s) => { return s.trim(); }); } if (Array.isArray(font)) { // Assume it's a previously migrated font-array. return font; } else if (typeof font === 'string') { return splitAndTrim(font); } else if (typeof font === 'object') { font.stops.forEach((stop) => { stop[1] = splitAndTrim(stop[1]); }); return font; } else { throw new Error('unexpected font value'); } } eachLayer(style, (layer) => { eachLayout(layer, (layout) => { if (layout['text-font']) { layout['text-font'] = migrateFontStack(layout['text-font']); } }); }); // Reverse order of symbol layers. This is an imperfect migration. // // The order of a symbol layer in the layers list affects two things: // - how it is drawn relative to other layers (like oneway arrows below bridges) // - the placement priority compared to other layers // // It's impossible to reverse the placement priority without breaking the draw order // in some cases. This migration only reverses the order of symbol layers that // are above all other types of layers. // // Symbol layers that are at the top of the map preserve their priority. // Symbol layers that are below another type (line, fill) of layer preserve their draw order. let firstSymbolLayer = 0; for (let i = style.layers.length - 1; i >= 0; i--) { const layer = style.layers[i]; if (layer.type !== 'symbol') { firstSymbolLayer = i + 1; break; } } const symbolLayers = style.layers.splice(firstSymbolLayer); symbolLayers.reverse(); style.layers = style.layers.concat(symbolLayers); return style; }