From e452aad118444327293d3296016ce67f528d53f4 Mon Sep 17 00:00:00 2001 From: Lauren Gutierrez <95086147+lauble@users.noreply.github.com> Date: Tue, 14 Nov 2023 10:28:24 -0500 Subject: [PATCH] create ColorSystem class and colors.json file (#1190) update DataLoader to load color info from colors.json and update core/index.js to include references to ColorSystem add ColorSystem initAsync to Context.js, create getColorScheme method on ColorSystem class, import getColorScheme in styles.js start color refactor in styles.js bind getColorScheme method to 'this' and update constructor refactor styles.js into STYLES class and update import statements to reflect changes remove initAsync() call in Context.js file change class name to 'Styles', update imports, change DEFAULT to 'default' update styleMatch method, change all vars to static vars, fix formatting add script to build_data.js to minify colors.json remove static colors.json import, refactor getColorScheme method start Styles refactor to StyleSystem, move file to /modules/core, update import statements add StyleSystem reference to available systems set add styles system dependency to MapSystem remove StyleSystem import, update style vars to use context.systems refactor StyleSystem initialize colorData as class var, move getDataAsync function to startAsync remove StyleSystem import, set style var with context delete styles.js update MapSystem.Test to include colors and styles --- data/colors.json | 38 ++ modules/core/ColorSystem.js | 43 +++ modules/core/DataLoaderSystem.js | 2 + modules/core/MapSystem.js | 4 +- modules/core/StyleSystem.js | 625 ++++++++++++++++++++++++++++++ modules/core/index.js | 8 +- modules/pixi/PixiLayerOsm.js | 7 +- modules/pixi/styles.js | 637 ------------------------------- modules/ui/map3d_viewer.js | 6 +- modules/ui/preset_icon.js | 3 +- scripts/build_data.js | 1 + test/spec/core/MapSystem.Test.js | 4 +- 12 files changed, 730 insertions(+), 648 deletions(-) create mode 100644 data/colors.json create mode 100644 modules/core/ColorSystem.js create mode 100644 modules/core/StyleSystem.js delete mode 100644 modules/pixi/styles.js diff --git a/data/colors.json b/data/colors.json new file mode 100644 index 0000000000..eb39725146 --- /dev/null +++ b/data/colors.json @@ -0,0 +1,38 @@ +{ + "red": { + "fill": { "color": "0xe06e5f", "alpha": 0.3 } + }, + "green": { + "fill": { "color": "0x8cd05f", "alpha": 0.3 } + }, + "blue": { + "fill": { "color": "0x77d4de", "alpha": 0.3 } + }, + "yellow": { + "fill": { "color": "0xffff94", "alpha": 0.25 } + }, + "gold": { + "fill": { "color": "0xc4be19", "alpha": 0.3 } + }, + "orange": { + "fill": { "color": "0xd6881a", "alpha": 0.3 } + }, + "pink": { + "fill": { "color": "0xe3a4f5", "alpha": 0.3 } + }, + "teal": { + "fill": { "color": "0x99e1aa", "alpha": 0.3 } + }, + "lightgreen": { + "fill": { "color": "0xbee83f", "alpha": 0.3 } + }, + "tan": { + "fill": { "color": "0xf5dcba", "alpha": 0.3 } + }, + "darkgray": { + "fill": { "color": "0x8c8c8c", "alpha": 0.5 } + }, + "lightgray": { + "fill": { "color": "0xaaaaaa", "alpha": 0.3 } + } + } \ No newline at end of file diff --git a/modules/core/ColorSystem.js b/modules/core/ColorSystem.js new file mode 100644 index 0000000000..64f0eaa852 --- /dev/null +++ b/modules/core/ColorSystem.js @@ -0,0 +1,43 @@ +import { AbstractSystem } from './AbstractSystem'; + +export class ColorSystem extends AbstractSystem { + constructor(context) { + super(context); + this.id = 'colors'; + this.context = context; + this.dependencies = new Set(['dataloader']); + this.autoStart = true; + this._started = false; + this.colorData = null; + + this.getColorScheme = this.getColorScheme.bind(this); + } + + initAsync(){ + for (const id of this.dependencies) { + if (!this.context.systems[id]) { + return Promise.reject(`Cannot init: ${this.id} requires ${id}`); + } + } + return Promise.resolve(); + } + + startAsync() { + this._started = true; + const context = this.context; + const dataloader = context.systems.dataloader; + + dataloader.getDataAsync('colors') + .then((data) => { this.colorData = data; } ); + + return Promise.resolve(); + } + + resetAsync() { + return Promise.resolve(); + } + + getColorScheme() { + return this.colorData; + } +} \ No newline at end of file diff --git a/modules/core/DataLoaderSystem.js b/modules/core/DataLoaderSystem.js index ffcd37cca0..50c53ed42e 100644 --- a/modules/core/DataLoaderSystem.js +++ b/modules/core/DataLoaderSystem.js @@ -44,6 +44,7 @@ export class DataLoaderSystem extends AbstractSystem { fileMap.set('shortcuts', 'data/shortcuts.min.json'); fileMap.set('territory_languages', 'data/territory_languages.min.json'); fileMap.set('wmf_sitematrix', 'https://cdn.jsdelivr.net/npm/wmf-sitematrix@0.1/wikipedia.min.json'); + fileMap.set('colors', 'data/colors.min.json'); this.fileMap = fileMap; this._cachedData = {}; @@ -74,6 +75,7 @@ export class DataLoaderSystem extends AbstractSystem { c.shortcuts = []; c.territory_languages = {}; c.wmf_sitematrix = [ ['English','English','en'], ['German', 'Deutsch', 'de'] ]; + c.colors = {}; } } diff --git a/modules/core/MapSystem.js b/modules/core/MapSystem.js index 886d22aabd..8f774898d1 100644 --- a/modules/core/MapSystem.js +++ b/modules/core/MapSystem.js @@ -48,7 +48,7 @@ export class MapSystem extends AbstractSystem { constructor(context) { super(context); this.id = 'map'; - this.dependencies = new Set(['editor', 'filters', 'imagery', 'photos', 'storage', 'l10n', 'urlhash']); + this.dependencies = new Set(['editor', 'filters', 'imagery', 'photos', 'storage', 'l10n', 'urlhash', 'colors', 'styles']); this.supersurface = d3_select(null); // parent `div` temporary zoom/pan transform this.surface = d3_select(null); // sibling `canvas` @@ -242,6 +242,7 @@ export class MapSystem extends AbstractSystem { const filters = context.systems.filters; const imagery = context.systems.imagery; const photos = context.systems.photos; + const colors = context.systems.colors; const scene = this._renderer.scene; editor @@ -302,6 +303,7 @@ export class MapSystem extends AbstractSystem { imagery.on('imagerychange', this.immediateRedraw); photos.on('photochange', this.immediateRedraw); scene.on('layerchange', this.immediateRedraw); + colors.on('colorchange', this.immediateRedraw); const osm = context.services.osm; if (osm) { diff --git a/modules/core/StyleSystem.js b/modules/core/StyleSystem.js new file mode 100644 index 0000000000..473afe0875 --- /dev/null +++ b/modules/core/StyleSystem.js @@ -0,0 +1,625 @@ +import * as PIXI from 'pixi.js'; +import { osmPavedTags } from '../osm/tags'; +import { AbstractSystem } from './AbstractSystem'; + +// +// A "style" is a bundle of properties to say how things should look. +// Each "style" looks like this: +// +// stylename: { +// fill: { props }, +// casing: { props }, +// stroke: { props } +// } +// +// Available property groups: +// `fill` - properties used when drawing feature as a filled area +// `casing` - properties used when drawing feature as a line (casing draws below stroke) +// `stroke` - properties used when drawing feature as a line +// +// Available properties: +// `width` - line width in pixel (for fills, this is the width of the outline) +// `color` - the color +// `alpha` - 0 = transparent/invisible, 1 = filled +// `cap` - `PIXI.LINE_CAP.` `BUTT`, `SQUARE`, or `ROUND` +// `join` - `PIXI.LINE_JOIN.` `BEVEL`, `MITER`, or `ROUND` +// `dash` - array of pixels on/off - e.g. `[20, 5, 5, 5]` +// +// The fill group also supports: +// `pattern` - supported pattern (see dist/img/pattern/* for these) +// +// Anything missing will just be pulled from the DEFAULT style. +// + +export class StyleSystem extends AbstractSystem { + /** + * @constructor + * @param `context` Global shared application context + */ + + constructor(context) { + super(context); + this.id = 'styles'; + this.context = context; + this.dependencies = new Set(['dataloader', 'colors']); + this.autoStart = true; + this._started = false; + + this.DEFAULTS = { + fill: { width: 2, color: 0xaaaaaa, alpha: 0.3 }, + casing: { width: 5, color: 0x444444, alpha: 1, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND }, + stroke: { width: 3, color: 0xcccccc, alpha: 1, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND } + }; + + this.WAYS = { + motorway: { + casing: { width: 10, color: 0x70372f }, + stroke: { width: 8, color: 0xcf2081 } + }, + trunk: { + casing: { width: 10, color: 0x70372f }, + stroke: { width: 8, color: 0xdd2f22 } + }, + primary: { + casing: { width: 10, color: 0x70372f }, + stroke: { width: 8, color: 0xf99806 } + }, + secondary: { + casing: { width: 10, color: 0x70372f }, + stroke: { width: 8, color: 0xf3f312 } + }, + tertiary: { + casing: { width: 10, color: 0x70372f }, + stroke: { width: 8, color: 0xfff9b3 } + }, + unclassified: { + casing: { width: 10, color: 0x444444 }, + stroke: { width: 8, color: 0xddccaa } + }, + residential: { + casing: { width: 10, color: 0x444444 }, + stroke: { width: 8, color: 0xffffff } + }, + living_street: { + casing: { width: 7, color: 0xffffff }, + stroke: { width: 5, color: 0xcccccc } + }, + service: { + casing: { width: 7, color: 0x444444 }, + stroke: { width: 5, color: 0xffffff } + }, + special_service: { + casing: { width: 7, color: 0x444444 }, + stroke: { width: 5, color: 0xddccaa } + }, + track: { + casing: { width: 7, color: 0x746f6f }, + stroke: { width: 5, color: 0xc5b59f } + }, + proposed: { + stroke: {width: 8, color: 0xCCCCCC, dash: [7 , 3], cap: PIXI.LINE_CAP.BUTT} + }, + pedestrian: { + casing: { width: 7, color: 0xffffff }, + stroke: { width: 5, color: 0x998888, dash: [8, 8], cap: PIXI.LINE_CAP.BUTT } + }, + path: { + casing: { width: 5, color: 0xddccaa }, + stroke: { width: 3, color: 0x998888, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } + }, + footway: { + casing: { width: 5, color: 0xffffff }, + stroke: { width: 3, color: 0x998888, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } + }, + crossing_marked: { + casing: { width: 5, color: 0xddccaa }, + stroke: { width: 3, color: 0x4c4444, dash: [6, 3], cap: PIXI.LINE_CAP.BUTT } + }, + crossing_unmarked: { + casing: { width: 5, color: 0xddccaa }, + stroke: { width: 3, color: 0x776a6a, dash: [6, 4], cap: PIXI.LINE_CAP.BUTT } + }, + cycleway: { + casing: { width: 5, color: 0xffffff }, + stroke: { width: 3, color: 0x58a9ed, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } + }, + bridleway: { + casing: { width: 5, color: 0xffffff }, + stroke: { width: 3, color: 0xe06d5f, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } + }, + corridor: { + casing: { width: 5, color: 0xffffff }, + stroke: { width: 3, color: 0x8cd05f, dash: [2, 8], cap: PIXI.LINE_CAP.ROUND } + }, + steps: { + casing: { width: 5, color: 0xffffff }, + stroke: { width: 3, color: 0x81d25c, dash: [3, 3], cap: PIXI.LINE_CAP.BUTT } + }, + river: { + casing: { width: 10, color: 0x444444 }, + stroke: { width: 8, color: 0x77dddd } + }, + stream: { + casing: { width: 7, color: 0x444444 }, + stroke: { width: 5, color: 0x77dddd } + }, + stream_intermittent: { + casing: { width: 7, color: 0x444444, cap: PIXI.LINE_CAP.BUTT }, + stroke: { width: 5, color: 0x77dddd, dash: [7, 3], cap: PIXI.LINE_CAP.BUTT, } + }, + ridge: { + stroke: { width: 2, color: 0x8cd05f} // rgb(140, 208, 95) + }, + runway: { + casing: { width: 10, color: 0x000000, cap: PIXI.LINE_CAP.BUTT }, + stroke: { width: 8, color: 0xffffff, dash: [24, 48], cap: PIXI.LINE_CAP.BUTT } + }, + taxiway: { + casing: { width: 7, color: 0x444444 }, + stroke: { width: 5, color: 0xffff00 } + }, + railway: { + casing: { width: 7, color: 0x555555, cap: PIXI.LINE_CAP.BUTT }, + stroke: { width: 2, color: 0xeeeeee, dash: [12, 12], cap: PIXI.LINE_CAP.BUTT, } + }, + railway_abandoned: { + stroke: {dash: [7, 3], cap: PIXI.LINE_CAP.BUTT } + }, + ferry: { + casing: { alpha: 0 }, // disable + stroke: { width: 3, color: 0x58a9ed, dash: [12, 8], cap: PIXI.LINE_CAP.BUTT } + }, + boundary: { + casing: { width: 6, color: 0x82b5fe, cap: PIXI.LINE_CAP.BUTT }, + stroke: { width: 2, color: 0xffffff, dash: [20, 5, 5, 5], cap: PIXI.LINE_CAP.BUTT } + }, + boundary_park: { + casing: { width: 6, color: 0x82b5fe, cap: PIXI.LINE_CAP.BUTT }, + stroke: { width: 2, color: 0xb0e298, dash: [20, 5, 5, 5], cap: PIXI.LINE_CAP.BUTT } + }, + barrier: { + casing: { alpha: 0 }, // disable + stroke: { width: 3, color: 0xdddddd, dash: [10, 5, 1, 5], cap: PIXI.LINE_CAP.ROUND } + }, + barrier_wall: { + casing: { alpha: 0 }, // disable + stroke: { width: 3, color: 0xdddddd, dash: [10, 5, 1, 5], cap: PIXI.LINE_CAP.ROUND } + }, + barrier_hedge: { + fill: { color: 0x8cd05f, alpha: 0.3 }, // rgb(140, 208, 95) + casing: { alpha: 0 }, // disable + stroke: { width: 3, color: 0x8cd05f, dash: [10, 5, 1, 5], cap: PIXI.LINE_CAP.ROUND } + }, + tree_row: { + casing: { width: 7, color: 0x444444 }, + stroke: { width: 5, color: 0x8cd05f } + }, + construction: { + casing: { width: 10, color: 0xffffff}, + stroke: { width: 8, color: 0xfc6c14, cap: PIXI.LINE_CAP.BUTT, dash: [10, 10] }, + }, + pipeline: { + casing: { width: 7, color: 0x666}, + stroke: { width: 5, color: 0xcbd0d8, dash: [80, 1.25], cap: PIXI.LINE_CAP.BUTT} + }, + abandoned: { + stroke: { width: 27, color: 0xcbd0d8, dash: [7, 3], cap: PIXI.LINE_CAP.BUTT } + }, + }; + + // + // A "style selector" contains OSM key/value tags to match to a style. + // Each "style selector" looks like this: + // + // osmkey: { + // osmvalue: stylename + // } + // + // Can use the value '*' to match any osmvalue. + // + // Important: The fewer rules in the selector, the more selective it is. + // For example: + // The `amenity` selector has 8 rules in it + // The `building` selector has 1 rule in it + // + // So a feature with both `amenity=kindergarden` and `building=yes` tags + // will be styled with the `building` rule. + // + + this.STYLE_SELECTORS = { + aeroway: { + planned: 'proposed', + proposed: 'proposed', + runway: 'runway', + taxiway: 'taxiway' + }, + amenity: { + childcare: 'yellow', + college: 'yellow', + fountain: 'blue', + kindergarten: 'yellow', + parking: 'darkgray', + research_institute: 'yellow', + school: 'yellow', + university: 'yellow' + }, + building: { + '*': 'red' + }, + barrier: { + city_wall: 'barrier_wall', + hedge: 'barrier_hedge', + retaining_wall: 'barrier_wall', + wall: 'barrier_wall', + '*': 'barrier' + }, + boundary: { + protected_area: 'boundary_park', + national_park: 'boundary_park', + '*': 'boundary' + }, + crossing: { + marked: 'crossing_marked', + traffic_signals: 'crossing_marked', + uncontrolled: 'crossing_marked', + zebra: 'crossing_marked', + '*': 'crossing_unmarked' + }, + golf: { + green: 'lightgreen' + }, + highway: { + abandoned: 'abandoned', + bridleway: 'bridleway', + bus_guideway: 'railway', + busway: 'special_service', + corridor: 'corridor', + construction: 'construction', + cycleway: 'cycleway', + footway: 'footway', + living_street: 'living_street', + living_street_link: 'living_street', + motorway: 'motorway', + motorway_link: 'motorway', + path: 'path', + pedestrian: 'pedestrian', + planned: 'proposed', + primary: 'primary', + primary_link: 'primary', + proposed: 'proposed', + residential: 'residential', + residential_link: 'residential', + secondary: 'secondary', + secondary_link: 'secondary', + service: 'service', + service_link: 'service', + steps: 'steps', + tertiary: 'tertiary', + tertiary_link: 'tertiary', + track: 'track', + trunk: 'trunk', + trunk_link: 'trunk', + unclassified: 'unclassified', + unclassified_link: 'unclassified' + }, + landuse: { + cemetery: 'lightgreen', + commercial: 'orange', + construction: 'gold', + farmland: 'lightgreen', + farmyard: 'tan', + flowerbed: 'green', + forest: 'green', + grass: 'green', + industrial: 'pink', + landfill: 'orange', + meadow: 'lightgreen', + military: 'orange', + orchard: 'lightgreen', + quarry: 'darkgray', + railway: 'darkgray', + recreation_ground: 'green', + residential: 'gold', + retail: 'orange', + village_green: 'green', + vineyard: 'lightgreen' + }, + leisure: { + garden: 'green', + golf_course: 'green', + nature_reserve: 'green', + park: 'green', + pitch: 'green', + swimming_pool: 'blue', + track: 'yellow' + }, + man_made: { + adit: 'darkgray', + breakwater: 'barrier_wall', + groyne: 'barrier_wall', + pipeline: 'pipeline' + }, + military: { + '*': 'orange' + }, + natural: { + bare_rock: 'darkgray', + bay: 'blue', + beach: 'yellow', + cave_entrance: 'darkgray', + cliff: 'darkgray', + glacier: 'lightgray', + ridge: 'ridge', + rock: 'darkgray', + sand: 'yellow', + scree: 'darkgray', + scrub: 'yellow', + shingle: 'darkgray', + stone: 'darkgray', + tree_row: 'tree_row', + water: 'blue', + wetland: 'teal', + '*': 'green' + }, + power: { + 'plant': 'pink' + }, + railway: { + abandoned: 'railway_abandoned', + planned: 'proposed', + platform: 'footway', + proposed: 'proposed', + '*': 'railway' + }, + route: { + 'ferry': 'ferry' + }, + sport: { + baseball: 'yellow', + basketball: 'darkgray', + beachvolleyball: 'yellow', + skateboard: 'darkgray', + softball: 'yellow' + }, + type: { + waterway: 'river' + }, + waterway: { + river: 'river', + dam: 'default', + weir: 'default', + '*': 'stream' + }, + service: { + alley: 'special_service', + driveway: 'special_service', + 'drive-through': 'special_service', + parking_aisle: 'special_service', + '*': 'special_service' + }, + intermittent: { + yes: 'stream_intermittent', + }, + proposed: { + yes: 'proposed', + }, + }; + + + // + // "pattern selectors" work exactly like style selectors. + // They contain OSM key/value tags to match to a pattern. + // + // osmkey: { + // osmvalue: patternname + // } + // + + this.PATTERN_SELECTORS = { + amenity: { + fountain: 'water_standing', + grave_yard: 'cemetery' + }, + golf: { + green: 'grass' + }, + landuse: { + cemetery: 'cemetery', + construction: 'construction', + farmland: 'farmland', + farmyard: 'farmyard', + forest: 'forest', + grass: 'grass', + grave_yard: 'cemetery', + landfill: 'landfill', + meadow: 'grass', + military: 'construction', + orchard: 'orchard', + quarry: 'quarry', + vineyard: 'vineyard' + }, + leaf_type: { + broadleaved: 'forest_broadleaved', + leafless: 'forest_leafless', + needleleaved: 'forest_needleleaved' + }, + natural: { + beach: 'dots', + grassland: 'grass', + sand: 'dots', + scrub: 'bushes', + water: 'waves', + wetland: 'wetland', + wood: 'forest' + }, + religion: { + buddhist: 'cemetery_buddhist', + christian: 'cemetery_christian', + jewish: 'cemetery_jewish', + muslim: 'cemetery_muslim' + }, + surface: { + grass: 'grass' + }, + water: { + pond: 'pond', + reservoir: 'lines' + }, + wetland: { + bog: 'wetland_bog', + marsh: 'wetland_marsh', + reedbed: 'wetland_reedbed', + swamp: 'wetland_swamp' + }, + }; + + + this.ROADS = { + motorway: true, + motorway_link: true, + trunk: true, + trunk_link: true, + primary: true, + primary_link: true, + secondary: true, + secondary_link: true, + tertiary: true, + tertiary_link: true, + unclassified: true, + unclassified_link: true, + residential: true, + residential_link: true, + living_street: true, + living_street_link: true, + service: true, + service_link: true, + bus_guideway: true, + track: true + }; + + this.styleMatch = this.styleMatch.bind(this); + } + + initAsync(){ + for (const id of this.dependencies) { + if (!this.context.systems[id]) { + return Promise.reject(`Cannot init: ${this.id} requires ${id}`); + } + } + return Promise.resolve(); + } + + startAsync() { + this._started = true; + return Promise.resolve(); + } + + resetAsync() { + return Promise.resolve(); + } + + styleMatch(tags) { + let matched = this.DEFAULTS; + let selectivity = 999; + let context = this.context; + let colors = context.systems.colors.getColorScheme(); + + for (const [k, v] of Object.entries(tags)) { + const group = this.STYLE_SELECTORS[k]; + if (!group || !v) continue; + + // smaller groups are more selective + const groupsize = Object.keys(group).length; + const stylename = group[v] || group['*']; // fallback value + + if (stylename && groupsize <= selectivity) { + if (!colors[stylename] && !this.WAYS[stylename]) { + console.error(`invalid stylename: ${stylename}`); // eslint-disable-line + continue; + } + matched = !colors[stylename] ? this.WAYS[stylename] : colors[stylename]; + selectivity = groupsize; + if (selectivity === 1) break; // no need to keep looking at tags + } + } + + // copy style, filling in defaults + let style = {}; + for (const group of ['fill', 'casing', 'stroke']) { + style[group] = {}; + for (const prop of ['width', 'color', 'alpha', 'cap', 'dash']) { + let value = matched[group] && matched[group][prop]; + if (value !== undefined) { + style[group][prop] = value; + continue; + } + let fallback = this.DEFAULTS[group] && this.DEFAULTS[group][prop]; + if (fallback !== undefined) { + style[group][prop] = fallback; + } + } + } + + // Apply casing/stroke overrides + const bridge = getTag(tags, 'bridge'); + const building = getTag(tags, 'building'); + const cutting = getTag(tags, 'cutting'); + const embankment = getTag(tags, 'embankment'); + const highway = getTag(tags, 'highway'); + const tracktype = getTag(tags, 'tracktype'); + const tunnel = getTag(tags, 'tunnel'); + let surface = getTag(tags, 'surface'); + if (highway === 'track' && tracktype !== 'grade1') { + surface = surface || 'dirt'; // default unimproved (non-grade1) tracks to 'dirt' surface + } + + if (bridge || embankment || cutting) { + style.casing.width += 7; + style.casing.color = 0x000000; + style.casing.cap = PIXI.LINE_CAP.BUTT; + if (embankment || cutting) { + style.casing.dash = [2, 4]; + } + } + if (tunnel) { + style.stroke.alpha = 0.5; + } + + if (surface && this.ROADS[highway] && !osmPavedTags.surface[surface]) { + if (!bridge) style.casing.color = 0xcccccc; + style.casing.cap = PIXI.LINE_CAP.BUTT; + style.casing.dash = [4, 4]; + } + + // Look for fill pattern + if (style.fill.pattern) return style; // already has a pattern defined by the style + if (building) return style; // don't apply patterns to buildings + + // Otherwise, look for a matching fill pattern. + selectivity = 999; + for (const k in tags) { + const v = tags[k]; + const group = this.PATTERN_SELECTORS[k]; + if (!group || !v) continue; + + // smaller groups are more selective + let groupsize = Object.keys(group).length; + let patternname = group[v]; + if (!patternname) patternname = group['*']; // fallback value + + if (patternname && groupsize <= selectivity) { + style.fill.pattern = patternname; + selectivity = groupsize; + if (selectivity === 1) break; // no need to keep looking at tags + } + } + + return style; + + + // This just returns the value of the tag, but ignores 'no' values + function getTag(tags, key) { + return tags[key] === 'no' ? undefined : tags[key]; + } + } +} \ No newline at end of file diff --git a/modules/core/index.js b/modules/core/index.js index 8acbe5f194..c02045bd01 100644 --- a/modules/core/index.js +++ b/modules/core/index.js @@ -17,6 +17,8 @@ import { UiSystem } from './UiSystem'; import { UploaderSystem } from './UploaderSystem'; import { UrlHashSystem } from './UrlHashSystem'; import { ValidationSystem } from './ValidationSystem'; +import { ColorSystem } from './ColorSystem'; +import { StyleSystem } from './StyleSystem'; export { AbstractSystem, @@ -35,7 +37,9 @@ export { UiSystem, UploaderSystem, UrlHashSystem, - ValidationSystem + ValidationSystem, + ColorSystem, + StyleSystem }; // At init time, we will instantiate any that are in the 'available' collection. @@ -59,3 +63,5 @@ systems.available.set('ui', UiSystem); systems.available.set('uploader', UploaderSystem); systems.available.set('urlhash', UrlHashSystem); systems.available.set('validator', ValidationSystem); +systems.available.set('colors', ColorSystem); +systems.available.set('styles', StyleSystem); diff --git a/modules/pixi/PixiLayerOsm.js b/modules/pixi/PixiLayerOsm.js index 07d23f60f6..6497a80b13 100644 --- a/modules/pixi/PixiLayerOsm.js +++ b/modules/pixi/PixiLayerOsm.js @@ -6,7 +6,6 @@ import { AbstractLayer } from './AbstractLayer'; import { PixiFeatureLine } from './PixiFeatureLine'; import { PixiFeaturePoint } from './PixiFeaturePoint'; import { PixiFeaturePolygon } from './PixiFeaturePolygon'; -import { styleMatch } from './styles'; const MINZOOM = 12; @@ -333,7 +332,8 @@ export class PixiLayerOsm extends AbstractLayer { if (feature.dirty) { const preset = presets.match(entity, graph); - const style = styleMatch(entity.tags); + const systems = this.context.systems; + const style = systems.styles.styleMatch(entity.tags); style.labelTint = style.fill.color ?? style.stroke.color ?? 0xeeeeee; feature.style = style; @@ -488,7 +488,8 @@ export class PixiLayerOsm extends AbstractLayer { } } - const style = styleMatch(tags); + const systems = this.context.systems; + const style = systems.styles.styleMatch(tags); // Todo: handle alternating/two-way case too if (geom === 'line') { style.lineMarkerName = entity.isOneWay() ? 'oneway' : ''; diff --git a/modules/pixi/styles.js b/modules/pixi/styles.js deleted file mode 100644 index 61fdfa7741..0000000000 --- a/modules/pixi/styles.js +++ /dev/null @@ -1,637 +0,0 @@ -import * as PIXI from 'pixi.js'; -import { osmPavedTags } from '../osm/tags'; - -// -// A "style" is a bundle of properties to say how things should look. -// Each "style" looks like this: -// -// stylename: { -// fill: { props }, -// casing: { props }, -// stroke: { props } -// } -// -// Available property groups: -// `fill` - properties used when drawing feature as a filled area -// `casing` - properties used when drawing feature as a line (casing draws below stroke) -// `stroke` - properties used when drawing feature as a line -// -// Available properties: -// `width` - line width in pixel (for fills, this is the width of the outline) -// `color` - the color -// `alpha` - 0 = transparent/invisible, 1 = filled -// `cap` - `PIXI.LINE_CAP.` `BUTT`, `SQUARE`, or `ROUND` -// `join` - `PIXI.LINE_JOIN.` `BEVEL`, `MITER`, or `ROUND` -// `dash` - array of pixels on/off - e.g. `[20, 5, 5, 5]` -// -// The fill group also supports: -// `pattern` - supported pattern (see dist/img/pattern/* for these) -// -// Anything missing will just be pulled from the DEFAULT style. -// - -export const STYLES = { - DEFAULT: { - fill: { width: 2, color: 0xaaaaaa, alpha: 0.3 }, - casing: { width: 5, color: 0x444444, alpha: 1, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND }, - stroke: { width: 3, color: 0xcccccc, alpha: 1, cap: PIXI.LINE_CAP.ROUND, join: PIXI.LINE_JOIN.ROUND } - }, - - red: { - fill: { color: 0xe06e5f, alpha: 0.3 } // rgb(224, 110, 95) - }, - green: { - fill: { color: 0x8cd05f, alpha: 0.3 } // rgb(140, 208, 95) - }, - blue: { - fill: { color: 0x77d4de, alpha: 0.3 } // rgb(119, 211, 222) - }, - yellow: { - fill: { color: 0xffff94, alpha: 0.25 } // rgb(255, 255, 148) - }, - gold: { - fill: { color: 0xc4be19, alpha: 0.3 } // rgb(196, 189, 25) - }, - orange: { - fill: { color: 0xd6881a, alpha: 0.3 } // rgb(214, 136, 26) - }, - pink: { - fill: { color: 0xe3a4f5, alpha: 0.3 } // rgb(228, 164, 245) - }, - teal: { - fill: { color: 0x99e1aa, alpha: 0.3 } // rgb(153, 225, 170) - }, - lightgreen: { - fill: { color: 0xbee83f, alpha: 0.3 } // rgb(191, 232, 63) - }, - tan: { - fill: { color: 0xf5dcba, alpha: 0.3 } // rgb(245, 220, 186) - }, - darkgray: { - fill: { color: 0x8c8c8c, alpha: 0.5 } // rgb(140, 140, 140) - }, - lightgray: { - fill: { color: 0xaaaaaa, alpha: 0.3 } // rgb(170, 170, 170) - }, - - motorway: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xcf2081 } - }, - trunk: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xdd2f22 } - }, - primary: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xf99806 } - }, - secondary: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xf3f312 } - }, - tertiary: { - casing: { width: 10, color: 0x70372f }, - stroke: { width: 8, color: 0xfff9b3 } - }, - unclassified: { - casing: { width: 10, color: 0x444444 }, - stroke: { width: 8, color: 0xddccaa } - }, - residential: { - casing: { width: 10, color: 0x444444 }, - stroke: { width: 8, color: 0xffffff } - }, - living_street: { - casing: { width: 7, color: 0xffffff }, - stroke: { width: 5, color: 0xcccccc } - }, - service: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xffffff } - }, - special_service: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xddccaa } - }, - track: { - casing: { width: 7, color: 0x746f6f }, - stroke: { width: 5, color: 0xc5b59f } - }, - proposed: { - stroke: {width: 8, color: 0xCCCCCC, dash: [7 , 3], cap: PIXI.LINE_CAP.BUTT} - }, - - - pedestrian: { - casing: { width: 7, color: 0xffffff }, - stroke: { width: 5, color: 0x998888, dash: [8, 8], cap: PIXI.LINE_CAP.BUTT } - }, - path: { - casing: { width: 5, color: 0xddccaa }, - stroke: { width: 3, color: 0x998888, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } - }, - footway: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x998888, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } - }, - crossing_marked: { - casing: { width: 5, color: 0xddccaa }, - stroke: { width: 3, color: 0x4c4444, dash: [6, 3], cap: PIXI.LINE_CAP.BUTT } - }, - crossing_unmarked: { - casing: { width: 5, color: 0xddccaa }, - stroke: { width: 3, color: 0x776a6a, dash: [6, 4], cap: PIXI.LINE_CAP.BUTT } - }, - cycleway: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x58a9ed, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } - }, - bridleway: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0xe06d5f, dash: [6, 6], cap: PIXI.LINE_CAP.BUTT } - }, - corridor: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x8cd05f, dash: [2, 8], cap: PIXI.LINE_CAP.ROUND } - }, - steps: { - casing: { width: 5, color: 0xffffff }, - stroke: { width: 3, color: 0x81d25c, dash: [3, 3], cap: PIXI.LINE_CAP.BUTT } - }, - - river: { - casing: { width: 10, color: 0x444444 }, - stroke: { width: 8, color: 0x77dddd } - }, - stream: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0x77dddd } - }, - stream_intermittent: { - casing: { width: 7, color: 0x444444, cap: PIXI.LINE_CAP.BUTT }, - stroke: { width: 5, color: 0x77dddd, dash: [7, 3], cap: PIXI.LINE_CAP.BUTT, } - }, - ridge: { - stroke: { width: 2, color: 0x8cd05f} // rgb(140, 208, 95) - }, - - runway: { - casing: { width: 10, color: 0x000000, cap: PIXI.LINE_CAP.BUTT }, - stroke: { width: 8, color: 0xffffff, dash: [24, 48], cap: PIXI.LINE_CAP.BUTT } - }, - taxiway: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0xffff00 } - }, - - railway: { - casing: { width: 7, color: 0x555555, cap: PIXI.LINE_CAP.BUTT }, - stroke: { width: 2, color: 0xeeeeee, dash: [12, 12], cap: PIXI.LINE_CAP.BUTT, } - }, - railway_abandoned: { - stroke: {dash: [7, 3], cap: PIXI.LINE_CAP.BUTT } - }, - - ferry: { - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0x58a9ed, dash: [12, 8], cap: PIXI.LINE_CAP.BUTT } - }, - - boundary: { - casing: { width: 6, color: 0x82b5fe, cap: PIXI.LINE_CAP.BUTT }, - stroke: { width: 2, color: 0xffffff, dash: [20, 5, 5, 5], cap: PIXI.LINE_CAP.BUTT } - }, - boundary_park: { - casing: { width: 6, color: 0x82b5fe, cap: PIXI.LINE_CAP.BUTT }, - stroke: { width: 2, color: 0xb0e298, dash: [20, 5, 5, 5], cap: PIXI.LINE_CAP.BUTT } - }, - - barrier: { - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0xdddddd, dash: [10, 5, 1, 5], cap: PIXI.LINE_CAP.ROUND } - }, - barrier_wall: { - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0xdddddd, dash: [10, 5, 1, 5], cap: PIXI.LINE_CAP.ROUND } - }, - barrier_hedge: { - fill: { color: 0x8cd05f, alpha: 0.3 }, // rgb(140, 208, 95) - casing: { alpha: 0 }, // disable - stroke: { width: 3, color: 0x8cd05f, dash: [10, 5, 1, 5], cap: PIXI.LINE_CAP.ROUND } - }, - - tree_row: { - casing: { width: 7, color: 0x444444 }, - stroke: { width: 5, color: 0x8cd05f } - }, - construction: { - casing: { width: 10, color: 0xffffff}, - stroke: { width: 8, color: 0xfc6c14, cap: PIXI.LINE_CAP.BUTT, dash: [10, 10] }, - }, - - pipeline: { - casing: { width: 7, color: 0x666}, - stroke: { width: 5, color: 0xcbd0d8, dash: [80, 1.25], cap: PIXI.LINE_CAP.BUTT} - }, - - abandoned: { - stroke: { width: 27, color: 0xcbd0d8, dash: [7, 3], cap: PIXI.LINE_CAP.BUTT } - }, -}; - - -// -// A "style selector" contains OSM key/value tags to match to a style. -// Each "style selector" looks like this: -// -// osmkey: { -// osmvalue: stylename -// } -// -// Can use the value '*' to match any osmvalue. -// -// Important: The fewer rules in the selector, the more selective it is. -// For example: -// The `amenity` selector has 8 rules in it -// The `building` selector has 1 rule in it -// -// So a feature with both `amenity=kindergarden` and `building=yes` tags -// will be styled with the `building` rule. -// - -const STYLE_SELECTORS = { - aeroway: { - planned: 'proposed', - proposed: 'proposed', - runway: 'runway', - taxiway: 'taxiway' - }, - amenity: { - childcare: 'yellow', - college: 'yellow', - fountain: 'blue', - kindergarten: 'yellow', - parking: 'darkgray', - research_institute: 'yellow', - school: 'yellow', - university: 'yellow' - }, - building: { - '*': 'red' - }, - barrier: { - city_wall: 'barrier_wall', - hedge: 'barrier_hedge', - retaining_wall: 'barrier_wall', - wall: 'barrier_wall', - '*': 'barrier' - }, - boundary: { - protected_area: 'boundary_park', - national_park: 'boundary_park', - '*': 'boundary' - }, - crossing: { - marked: 'crossing_marked', - traffic_signals: 'crossing_marked', - uncontrolled: 'crossing_marked', - zebra: 'crossing_marked', - '*': 'crossing_unmarked' - }, - golf: { - green: 'lightgreen' - }, - highway: { - abandoned: 'abandoned', - bridleway: 'bridleway', - bus_guideway: 'railway', - busway: 'special_service', - corridor: 'corridor', - construction: 'construction', - cycleway: 'cycleway', - footway: 'footway', - living_street: 'living_street', - living_street_link: 'living_street', - motorway: 'motorway', - motorway_link: 'motorway', - path: 'path', - pedestrian: 'pedestrian', - planned: 'proposed', - primary: 'primary', - primary_link: 'primary', - proposed: 'proposed', - residential: 'residential', - residential_link: 'residential', - secondary: 'secondary', - secondary_link: 'secondary', - service: 'service', - service_link: 'service', - steps: 'steps', - tertiary: 'tertiary', - tertiary_link: 'tertiary', - track: 'track', - trunk: 'trunk', - trunk_link: 'trunk', - unclassified: 'unclassified', - unclassified_link: 'unclassified' - }, - landuse: { - cemetery: 'lightgreen', - commercial: 'orange', - construction: 'gold', - farmland: 'lightgreen', - farmyard: 'tan', - flowerbed: 'green', - forest: 'green', - grass: 'green', - industrial: 'pink', - landfill: 'orange', - meadow: 'lightgreen', - military: 'orange', - orchard: 'lightgreen', - quarry: 'darkgray', - railway: 'darkgray', - recreation_ground: 'green', - residential: 'gold', - retail: 'orange', - village_green: 'green', - vineyard: 'lightgreen' - }, - leisure: { - garden: 'green', - golf_course: 'green', - nature_reserve: 'green', - park: 'green', - pitch: 'green', - swimming_pool: 'blue', - track: 'yellow' - }, - man_made: { - adit: 'darkgray', - breakwater: 'barrier_wall', - groyne: 'barrier_wall', - pipeline: 'pipeline' - }, - military: { - '*': 'orange' - }, - natural: { - bare_rock: 'darkgray', - bay: 'blue', - beach: 'yellow', - cave_entrance: 'darkgray', - cliff: 'darkgray', - glacier: 'lightgray', - ridge: 'ridge', - rock: 'darkgray', - sand: 'yellow', - scree: 'darkgray', - scrub: 'yellow', - shingle: 'darkgray', - stone: 'darkgray', - tree_row: 'tree_row', - water: 'blue', - wetland: 'teal', - '*': 'green' - }, - power: { - 'plant': 'pink' - }, - railway: { - abandoned: 'railway_abandoned', - planned: 'proposed', - platform: 'footway', - proposed: 'proposed', - '*': 'railway' - }, - route: { - 'ferry': 'ferry' - }, - sport: { - baseball: 'yellow', - basketball: 'darkgray', - beachvolleyball: 'yellow', - skateboard: 'darkgray', - softball: 'yellow' - }, - type: { - waterway: 'river' - }, - waterway: { - river: 'river', - dam: 'DEFAULT', - weir: 'DEFAULT', - '*': 'stream' - }, - service: { - alley: 'special_service', - driveway: 'special_service', - 'drive-through': 'special_service', - parking_aisle: 'special_service', - '*': 'special_service' - }, - intermittent: { - yes: 'stream_intermittent', - }, - proposed: { - yes: 'proposed', - }, -}; - - -// -// "pattern selectors" work exactly like style selectors. -// They contain OSM key/value tags to match to a pattern. -// -// osmkey: { -// osmvalue: patternname -// } -// - -const PATTERN_SELECTORS = { - amenity: { - fountain: 'water_standing', - grave_yard: 'cemetery' - }, - golf: { - green: 'grass' - }, - landuse: { - cemetery: 'cemetery', - construction: 'construction', - farmland: 'farmland', - farmyard: 'farmyard', - forest: 'forest', - grass: 'grass', - grave_yard: 'cemetery', - landfill: 'landfill', - meadow: 'grass', - military: 'construction', - orchard: 'orchard', - quarry: 'quarry', - vineyard: 'vineyard' - }, - leaf_type: { - broadleaved: 'forest_broadleaved', - leafless: 'forest_leafless', - needleleaved: 'forest_needleleaved' - }, - natural: { - beach: 'dots', - grassland: 'grass', - sand: 'dots', - scrub: 'bushes', - water: 'waves', - wetland: 'wetland', - wood: 'forest' - }, - religion: { - buddhist: 'cemetery_buddhist', - christian: 'cemetery_christian', - jewish: 'cemetery_jewish', - muslim: 'cemetery_muslim' - }, - surface: { - grass: 'grass' - }, - water: { - pond: 'pond', - reservoir: 'lines' - }, - wetland: { - bog: 'wetland_bog', - marsh: 'wetland_marsh', - reedbed: 'wetland_reedbed', - swamp: 'wetland_swamp' - }, -}; - - -const ROADS = { - motorway: true, - motorway_link: true, - trunk: true, - trunk_link: true, - primary: true, - primary_link: true, - secondary: true, - secondary_link: true, - tertiary: true, - tertiary_link: true, - unclassified: true, - unclassified_link: true, - residential: true, - residential_link: true, - living_street: true, - living_street_link: true, - service: true, - service_link: true, - bus_guideway: true, - track: true -}; - - -export function styleMatch(tags) { - let matched = STYLES.DEFAULT; - let selectivity = 999; - - for (const [k, v] of Object.entries(tags)) { - const group = STYLE_SELECTORS[k]; - if (!group || !v) continue; - - // smaller groups are more selective - const groupsize = Object.keys(group).length; - const stylename = group[v] || group['*']; // fallback value - - if (stylename && groupsize <= selectivity) { - if (!STYLES[stylename]) { - console.error(`invalid stylename: ${stylename}`); // eslint-disable-line - continue; - } - matched = STYLES[stylename]; - selectivity = groupsize; - if (selectivity === 1) break; // no need to keep looking at tags - } - } - - // copy style, filling in defaults - let style = {}; - for (const group of ['fill', 'casing', 'stroke']) { - style[group] = {}; - for (const prop of ['width', 'color', 'alpha', 'cap', 'dash']) { - let value = matched[group] && matched[group][prop]; - if (value !== undefined) { - style[group][prop] = value; - continue; - } - let fallback = STYLES.DEFAULT[group] && STYLES.DEFAULT[group][prop]; - if (fallback !== undefined) { - style[group][prop] = fallback; - } - } - } - - // Apply casing/stroke overrides - const bridge = getTag(tags, 'bridge'); - const building = getTag(tags, 'building'); - const cutting = getTag(tags, 'cutting'); - const embankment = getTag(tags, 'embankment'); - const highway = getTag(tags, 'highway'); - const tracktype = getTag(tags, 'tracktype'); - const tunnel = getTag(tags, 'tunnel'); - let surface = getTag(tags, 'surface'); - if (highway === 'track' && tracktype !== 'grade1') { - surface = surface || 'dirt'; // default unimproved (non-grade1) tracks to 'dirt' surface - } - - if (bridge || embankment || cutting) { - style.casing.width += 7; - style.casing.color = 0x000000; - style.casing.cap = PIXI.LINE_CAP.BUTT; - if (embankment || cutting) { - style.casing.dash = [2, 4]; - } - } - if (tunnel) { - style.stroke.alpha = 0.5; - } - - if (surface && ROADS[highway] && !osmPavedTags.surface[surface]) { - if (!bridge) style.casing.color = 0xcccccc; - style.casing.cap = PIXI.LINE_CAP.BUTT; - style.casing.dash = [4, 4]; - } - - // Look for fill pattern - if (style.fill.pattern) return style; // already has a pattern defined by the style - if (building) return style; // don't apply patterns to buildings - - // Otherwise, look for a matching fill pattern. - selectivity = 999; - for (const k in tags) { - const v = tags[k]; - const group = PATTERN_SELECTORS[k]; - if (!group || !v) continue; - - // smaller groups are more selective - let groupsize = Object.keys(group).length; - let patternname = group[v]; - if (!patternname) patternname = group['*']; // fallback value - - if (patternname && groupsize <= selectivity) { - style.fill.pattern = patternname; - selectivity = groupsize; - if (selectivity === 1) break; // no need to keep looking at tags - } - } - - return style; - - - // This just returns the value of the tag, but ignores 'no' values - function getTag(tags, key) { - return tags[key] === 'no' ? undefined : tags[key]; - } - -} diff --git a/modules/ui/map3d_viewer.js b/modules/ui/map3d_viewer.js index dcd8c514b2..e2f4185d19 100644 --- a/modules/ui/map3d_viewer.js +++ b/modules/ui/map3d_viewer.js @@ -1,7 +1,7 @@ import { Color } from 'pixi.js'; import { select as d3_select } from 'd3-selection'; -import { styleMatch } from '../pixi/styles'; +import { StyleSystem } from '../core/StyleSystem'; import { uiCmd } from './cmd'; import { geomPolygonContainsPolygon } from '@rapid-sdk/math'; @@ -156,7 +156,7 @@ export function uiMap3dViewer(context) { let gj = areaEnt.asGeoJSON(editor.staging.graph); if (gj.type !== 'Polygon' && gj.type !== 'MultiPolygon') continue; - const style = styleMatch(areaEnt.tags); + const style = StyleSystem.styleMatch(areaEnt.tags); const fillColor = new Color(style.fill.color).toHex(); const strokeColor = new Color(style.stroke.color).toHex(); @@ -193,7 +193,7 @@ export function uiMap3dViewer(context) { const gj = roadEnt.asGeoJSON(editor.staging.graph); if (gj.type !== 'LineString') continue; - const style = styleMatch(roadEnt.tags); + const style = StyleSystem.styleMatch(roadEnt.tags); const casingColor = new Color(style.casing.color).toHex(); const strokeColor = new Color(style.stroke.color).toHex(); diff --git a/modules/ui/preset_icon.js b/modules/ui/preset_icon.js index 51c66f776f..0e2ebada1c 100644 --- a/modules/ui/preset_icon.js +++ b/modules/ui/preset_icon.js @@ -1,5 +1,4 @@ import { Color } from 'pixi.js'; -import { styleMatch } from '../pixi/styles'; import { uiIcon } from './icon'; @@ -254,7 +253,7 @@ export function uiPresetIcon(context) { const showLine = isPreset && (geom === 'line'); const showArea = isPreset && (geom === 'area'); const showRoute = isPreset && (geom === 'route') && (p.id !== 'type/route'); - const style = styleMatch(tags); + const style = context.systems.styles.styleMatch(tags); container .classed('showing-img', !!imageURL); diff --git a/scripts/build_data.js b/scripts/build_data.js index 98fc915361..f81303709d 100644 --- a/scripts/build_data.js +++ b/scripts/build_data.js @@ -98,6 +98,7 @@ function buildData() { minifySync('data/qa_data.json', 'dist/data/qa_data.min.json'), minifySync('data/shortcuts.json', 'dist/data/shortcuts.min.json'), minifySync('data/territory_languages.json', 'dist/data/territory_languages.min.json') + minifySync('data/colors.json', 'dist/data/colors.min.json') return _currBuild = Promise.resolve(true) .then(() => { diff --git a/test/spec/core/MapSystem.Test.js b/test/spec/core/MapSystem.Test.js index a0e1ee6023..8f7427661f 100644 --- a/test/spec/core/MapSystem.Test.js +++ b/test/spec/core/MapSystem.Test.js @@ -45,7 +45,9 @@ describe('MapSystem', () => { photos: new MockSystem(), l10n: new MockLocalizationSystem(), storage: new MockStorageSystem(), - urlhash: new MockSystem() + urlhash: new MockSystem(), + colors: new MockSystem(), + styles: new MockSystem(), }; this.projection = new sdk.Projection(); }