From 2ccdc438f49b1ac9ba08080707816ec3bdf773fb Mon Sep 17 00:00:00 2001 From: Quincy Morgan Date: Mon, 30 Sep 2019 12:27:33 +0200 Subject: [PATCH] Render area fill patterns in preset icons (close #6900) --- modules/svg/areas.js | 135 +++---------------------------------- modules/svg/index.js | 1 + modules/svg/tag_pattern.js | 125 ++++++++++++++++++++++++++++++++++ modules/ui/preset_icon.js | 11 ++- 4 files changed, 144 insertions(+), 128 deletions(-) create mode 100644 modules/svg/tag_pattern.js diff --git a/modules/svg/areas.js b/modules/svg/areas.js index 5ba0d9cd5..b76c46e33 100644 --- a/modules/svg/areas.js +++ b/modules/svg/areas.js @@ -3,135 +3,18 @@ import { bisector as d3_bisector } from 'd3-array'; import { osmEntity, osmIsOldMultipolygonOuterMember } from '../osm'; import { svgPath, svgSegmentWay } from './helpers'; import { svgTagClasses } from './tag_classes'; +import { svgTagPattern } from './tag_pattern'; export function svgAreas(projection, context) { - // Patterns only work in Firefox when set directly on element. - // (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632) - var patterns = { - // tag - pattern name - // -or- - // tag - value - pattern name - // -or- - // tag - value - rules (optional tag-values, pattern name) - // (matches earlier rules first, so fallback should be last entry) - amenity: { - grave_yard: 'cemetery', - fountain: 'water_standing' - }, - landuse: { - cemetery: [ - { religion: 'christian', pattern: 'cemetery_christian' }, - { religion: 'buddhist', pattern: 'cemetery_buddhist' }, - { religion: 'muslim', pattern: 'cemetery_muslim' }, - { religion: 'jewish', pattern: 'cemetery_jewish' }, - { pattern: 'cemetery' } - ], - construction: 'construction', - farmland: 'farmland', - farmyard: 'farmyard', - forest: [ - { leaf_type: 'broadleaved', pattern: 'forest_broadleaved' }, - { leaf_type: 'needleleaved', pattern: 'forest_needleleaved' }, - { leaf_type: 'leafless', pattern: 'forest_leafless' }, - { pattern: 'forest' } // same as 'leaf_type:mixed' - ], - grave_yard: 'cemetery', - grass: 'grass', - landfill: 'landfill', - meadow: 'meadow', - military: 'construction', - orchard: 'orchard', - quarry: 'quarry', - vineyard: 'vineyard' - }, - natural: { - beach: 'beach', - grassland: 'grass', - sand: 'beach', - scrub: 'scrub', - water: [ - { water: 'pond', pattern: 'pond' }, - { water: 'reservoir', pattern: 'water_standing' }, - { pattern: 'waves' } - ], - wetland: [ - { wetland: 'marsh', pattern: 'wetland_marsh' }, - { wetland: 'swamp', pattern: 'wetland_swamp' }, - { wetland: 'bog', pattern: 'wetland_bog' }, - { wetland: 'reedbed', pattern: 'wetland_reedbed' }, - { pattern: 'wetland' } - ], - wood: [ - { leaf_type: 'broadleaved', pattern: 'forest_broadleaved' }, - { leaf_type: 'needleleaved', pattern: 'forest_needleleaved' }, - { leaf_type: 'leafless', pattern: 'forest_leafless' }, - { pattern: 'forest' } // same as 'leaf_type:mixed' - ] - }, - traffic_calming: { - island: [ - { surface: 'grass', pattern: 'grass' }, - ], - chicane: [ - { surface: 'grass', pattern: 'grass' }, - ], - choker: [ - { surface: 'grass', pattern: 'grass' }, - ] + + + function getPatternStyle(tags) { + var imageID = svgTagPattern(tags); + if (imageID) { + return 'url("#' + imageID + '")'; } - }; - - function setPattern(entity) { - // Skip pattern filling if this is a building (buildings don't get patterns applied) - if (entity.tags.building && entity.tags.building !== 'no') { - this.style.fill = this.style.stroke = ''; - return; - } - - for (var tag in patterns) { - var entityValue = entity.tags[tag]; - if (!entityValue) continue; - - if (typeof patterns[tag] === 'string') { // extra short syntax (just tag) - pattern name - this.style.fill = this.style.stroke = 'url("#pattern-' + patterns[tag] + '")'; - return; - } else { - var values = patterns[tag]; - for (var value in values) { - if (entityValue !== value) continue; - - var rules = values[value]; - if (typeof rules === 'string') { // short syntax - pattern name - this.style.fill = this.style.stroke = 'url("#pattern-' + rules + '")'; - return; - } else { // long syntax - rule array - for (var ruleKey in rules) { - var rule = rules[ruleKey]; - - var pass = true; - for (var criterion in rule) { - if (criterion !== 'pattern') { // reserved for pattern name - // The only rule is a required tag-value pair - var v = entity.tags[criterion]; - if (!v || v !== rule[criterion]) { - pass = false; - break; - } - } - } - - if (pass) { - this.style.fill = this.style.stroke = 'url("#pattern-' + rule.pattern + '")'; - return; - } - } - } - } - } - } - - this.style.fill = this.style.stroke = ''; + return ''; } @@ -284,7 +167,7 @@ export function svgAreas(projection, context) { if (layer === 'fill') { this.setAttribute('clip-path', 'url(#' + entity.id + '-clippath)'); - setPattern.call(this, entity); + this.style.fill = this.style.stroke = getPatternStyle(entity.tags); } }) .call(svgTagClasses()) diff --git a/modules/svg/index.js b/modules/svg/index.js index 82d6bfe9a..40554720f 100644 --- a/modules/svg/index.js +++ b/modules/svg/index.js @@ -23,6 +23,7 @@ export { svgRelationMemberTags } from './helpers.js'; export { svgSegmentWay } from './helpers.js'; export { svgStreetside } from './streetside.js'; export { svgTagClasses } from './tag_classes.js'; +export { svgTagPattern } from './tag_pattern.js'; export { svgTouch } from './touch.js'; export { svgTurns } from './turns.js'; export { svgVertices } from './vertices.js'; diff --git a/modules/svg/tag_pattern.js b/modules/svg/tag_pattern.js new file mode 100644 index 000000000..a286e1e5b --- /dev/null +++ b/modules/svg/tag_pattern.js @@ -0,0 +1,125 @@ + +// Patterns only work in Firefox when set directly on element. +// (This is not a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=750632) +var patterns = { + // tag - pattern name + // -or- + // tag - value - pattern name + // -or- + // tag - value - rules (optional tag-values, pattern name) + // (matches earlier rules first, so fallback should be last entry) + amenity: { + grave_yard: 'cemetery', + fountain: 'water_standing' + }, + landuse: { + cemetery: [ + { religion: 'christian', pattern: 'cemetery_christian' }, + { religion: 'buddhist', pattern: 'cemetery_buddhist' }, + { religion: 'muslim', pattern: 'cemetery_muslim' }, + { religion: 'jewish', pattern: 'cemetery_jewish' }, + { pattern: 'cemetery' } + ], + construction: 'construction', + farmland: 'farmland', + farmyard: 'farmyard', + forest: [ + { leaf_type: 'broadleaved', pattern: 'forest_broadleaved' }, + { leaf_type: 'needleleaved', pattern: 'forest_needleleaved' }, + { leaf_type: 'leafless', pattern: 'forest_leafless' }, + { pattern: 'forest' } // same as 'leaf_type:mixed' + ], + grave_yard: 'cemetery', + grass: 'grass', + landfill: 'landfill', + meadow: 'meadow', + military: 'construction', + orchard: 'orchard', + quarry: 'quarry', + vineyard: 'vineyard' + }, + natural: { + beach: 'beach', + grassland: 'grass', + sand: 'beach', + scrub: 'scrub', + water: [ + { water: 'pond', pattern: 'pond' }, + { water: 'reservoir', pattern: 'water_standing' }, + { pattern: 'waves' } + ], + wetland: [ + { wetland: 'marsh', pattern: 'wetland_marsh' }, + { wetland: 'swamp', pattern: 'wetland_swamp' }, + { wetland: 'bog', pattern: 'wetland_bog' }, + { wetland: 'reedbed', pattern: 'wetland_reedbed' }, + { pattern: 'wetland' } + ], + wood: [ + { leaf_type: 'broadleaved', pattern: 'forest_broadleaved' }, + { leaf_type: 'needleleaved', pattern: 'forest_needleleaved' }, + { leaf_type: 'leafless', pattern: 'forest_leafless' }, + { pattern: 'forest' } // same as 'leaf_type:mixed' + ] + }, + traffic_calming: { + island: [ + { surface: 'grass', pattern: 'grass' }, + ], + chicane: [ + { surface: 'grass', pattern: 'grass' }, + ], + choker: [ + { surface: 'grass', pattern: 'grass' }, + ] + } +}; + +export function svgTagPattern(tags) { + // Skip pattern filling if this is a building (buildings don't get patterns applied) + if (tags.building && tags.building !== 'no') { + return null; + } + + for (var tag in patterns) { + var entityValue = tags[tag]; + if (!entityValue) continue; + + if (typeof patterns[tag] === 'string') { // extra short syntax (just tag) - pattern name + return 'pattern-' + patterns[tag]; + } else { + var values = patterns[tag]; + for (var value in values) { + if (entityValue !== value) continue; + + var rules = values[value]; + if (typeof rules === 'string') { // short syntax - pattern name + return 'pattern-' + rules; + } + + // long syntax - rule array + for (var ruleKey in rules) { + var rule = rules[ruleKey]; + + var pass = true; + for (var criterion in rule) { + if (criterion !== 'pattern') { // reserved for pattern name + // The only rule is a required tag-value pair + var v = tags[criterion]; + if (!v || v !== rule[criterion]) { + pass = false; + break; + } + } + } + + if (pass) { + return 'pattern-' + rule.pattern; + } + } + } + } + } + + return null; +} diff --git a/modules/ui/preset_icon.js b/modules/ui/preset_icon.js index 70ad9984f..dd379d1df 100644 --- a/modules/ui/preset_icon.js +++ b/modules/ui/preset_icon.js @@ -1,6 +1,8 @@ import { select as d3_select } from 'd3-selection'; -import { svgIcon, svgTagClasses } from '../svg'; +import { svgTagClasses } from '../svg/tag_classes'; +import { svgIcon } from '../svg/icon'; +import { svgTagPattern } from '../svg/tag_pattern'; import { utilFunctor } from '../util'; export function uiPresetIcon(context) { @@ -282,10 +284,15 @@ export function uiPresetIcon(context) { renderSquareFill(fillEnter); fill = fillEnter.merge(fill); + var patternID = svgTagPattern(tags); + var patternStyle = patternID ? 'url("#' + patternID + '")' : null; + fill.selectAll('path.stroke') .attr('class', 'area stroke ' + tagClasses); fill.selectAll('path.fill') - .attr('class', 'area fill ' + tagClasses); + .attr('class', 'area fill ' + tagClasses) + .style('fill', patternStyle) + .style('stroke', patternStyle); var line = container.selectAll('.preset-icon-line')