From 2dd7bfc849cabac7f8f18332746a24794e163343 Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Wed, 6 Jun 2018 02:19:38 -0400 Subject: [PATCH] Add support for FontAwesome icons as preset icons (closes #3025) --- README.md | 11 ++++ build_data.js | 66 +++++++++++++++---- data/presets/README.md | 1 + data/presets/presets.json | 12 ++-- .../presets/amenity/motorcycle_parking.json | 2 +- .../presets/emergency/ambulance_station.json | 2 +- data/presets/presets/shop/motorcycle.json | 2 +- .../presets/shop/motorcycle_repair.json | 2 +- data/taginfo.json | 8 +-- modules/svg/defs.js | 1 + modules/ui/preset_icon.js | 8 ++- package.json | 3 + svg/fontawesome/ambulance.svg | 1 + svg/fontawesome/motorcycle.svg | 1 + 14 files changed, 90 insertions(+), 30 deletions(-) create mode 100644 svg/fontawesome/ambulance.svg create mode 100644 svg/fontawesome/motorcycle.svg diff --git a/README.md b/README.md index 5910c2d18..6a84a4131 100644 --- a/README.md +++ b/README.md @@ -75,11 +75,22 @@ If you want to add in the full history later on, perhaps to run `git blame` or ` For guidance on building a packaged version, running tests, and contributing to development, see [CONTRIBUTING.md](CONTRIBUTING.md). + ## License iD is available under the [ISC License](https://opensource.org/licenses/ISC). See the [LICENSE.md](LICENSE.md) file for more details. +iD also bundles portions of the following open source software. +* [D3.js (BSD-3-Clause)](https://github.com/d3/d3) +* [editor-layer-index (CC-BY-SA 3.0)](https://github.com/osmlab/editor-layer-index) +* [Font Awesome (CC-BY 4.0)](https://fontawesome.com/license) +* [Maki (CC0 1.0)](https://github.com/mapbox/maki) +* [Mapillary JS (MIT)](https://github.com/mapillary/mapillary-js) +* [name-suggestion-index (BSD-3-Clause)](https://github.com/osmlab/name-suggestion-index) +* [osm-community-index (ISC)](https://github.com/osmlab/osm-community-index) + + ## Thank you Initial development of iD was made possible by a [grant of the Knight Foundation](https://www.mapbox.com/blog/knight-invests-openstreetmap/). diff --git a/build_data.js b/build_data.js index fae5c0a89..759ece65e 100644 --- a/build_data.js +++ b/build_data.js @@ -19,6 +19,11 @@ const fieldSchema = require('./data/presets/schema/field.json'); const presetSchema = require('./data/presets/schema/preset.json'); const suggestions = require('name-suggestion-index/name-suggestions.json'); +// fontawesome icons +const fontawesome = require('@fortawesome/fontawesome'); +const fas = require('@fortawesome/fontawesome-free-solid').default; +fontawesome.library.add(fas); + module.exports = function buildData() { var building; @@ -52,6 +57,9 @@ module.exports = function buildData() { presets: {} }; + // Font Awesome icons used + var faIcons = {}; + // Start clean shell.rm('-f', [ 'data/presets/categories.json', @@ -59,12 +67,13 @@ module.exports = function buildData() { 'data/presets/presets.json', 'data/presets.yaml', 'data/taginfo.json', - 'dist/locales/en.json' + 'dist/locales/en.json', + 'svg/fontawesome/*', ]); - var categories = generateCategories(tstrings); - var fields = generateFields(tstrings); - var presets = generatePresets(tstrings); + var categories = generateCategories(tstrings, faIcons); + var fields = generateFields(tstrings, faIcons); + var presets = generatePresets(tstrings, faIcons); var defaults = read('data/presets/defaults.json'); var translations = generateTranslations(fields, presets, tstrings); var taginfo = generateTaginfo(presets, fields); @@ -90,7 +99,8 @@ module.exports = function buildData() { ), writeFileProm('data/presets.yaml', translationsToYAML(translations)), writeFileProm('data/taginfo.json', JSON.stringify(taginfo, null, 4)), - writeEnJson(tstrings) + writeEnJson(tstrings), + writeFaIcons(faIcons) ]; return Promise.all(tasks) @@ -127,23 +137,28 @@ function validate(file, instance, schema) { } -function generateCategories(tstrings) { +function generateCategories(tstrings, faIcons) { var categories = {}; glob.sync(__dirname + '/data/presets/categories/*.json').forEach(function(file) { - var field = read(file); + var category = read(file); var id = 'category-' + path.basename(file, '.json'); - tstrings.categories[id] = { name: field.name }; - categories[id] = field; + tstrings.categories[id] = { name: category.name }; + categories[id] = category; + + // fontawesome icon, remember for later + if (/^fa-/.test(category.icon)) { + faIcons[category.icon] = {}; + } }); return categories; } -function generateFields(tstrings) { +function generateFields(tstrings, faIcons) { var fields = {}; glob.sync(__dirname + '/data/presets/fields/**/*.json').forEach(function(file) { - var field = read(file), - id = stripLeadingUnderscores(file.match(/presets\/fields\/([^.]*)\.json/)[1]); + var field = read(file); + var id = stripLeadingUnderscores(file.match(/presets\/fields\/([^.]*)\.json/)[1]); validate(file, field, fieldSchema); @@ -162,10 +177,16 @@ function generateFields(tstrings) { } fields[id] = field; + + // fontawesome icon, remember for later + if (/^fa-/.test(field.icon)) { + faIcons[field.icon] = {}; + } }); return fields; } + function suggestionsToPresets(presets) { var existing = {}; @@ -239,7 +260,7 @@ function stripLeadingUnderscores(str) { } -function generatePresets(tstrings) { +function generatePresets(tstrings, faIcons) { var presets = {}; glob.sync(__dirname + '/data/presets/presets/**/*.json').forEach(function(file) { @@ -254,6 +275,11 @@ function generatePresets(tstrings) { }; presets[id] = preset; + + // fontawesome icon, remember for later + if (/^fa-/.test(preset.icon)) { + faIcons[preset.icon] = {}; + } }); presets = _merge(presets, suggestionsToPresets(presets)); @@ -351,6 +377,9 @@ function generateTaginfo(presets, fields) { } else if (/^temaki-/.test(preset.icon)) { tag.icon_url = 'https://raw.githubusercontent.com/bhousel/temaki/master/icons/' + preset.icon.replace(/^temaki-/, '') + '.svg?sanitize=true'; + } else if (/^fa-/.test(preset.icon)) { + tag.icon_url = 'https://raw.githubusercontent.com/openstreetmap/iD/master/svg/fontawesome/' + + preset.icon.replace(/^fa-/, '') + '.svg?sanitize=true'; } coalesceTags(taginfo, tag); @@ -484,6 +513,7 @@ function translationsToYAML(translations) { .replace(/\'.*#\':/g, '#'); } + function writeEnJson(tstrings) { var readCoreYaml = readFileProm('data/core.yaml', 'utf8'); var readImagery = readFileProm('node_modules/editor-layer-index/i18n/en.yaml', 'utf8'); @@ -503,6 +533,16 @@ function writeEnJson(tstrings) { }); } + +function writeFaIcons(faIcons) { + for (var key in faIcons) { + var name = key.substring(3); // without `fa-` + var def = fontawesome.findIconDefinition({ iconName: name }); + writeFileProm('svg/fontawesome/' + name + '.svg', fontawesome.icon(def).html); + } +} + + function writeFileProm(path, content) { return new Promise(function(res, rej) { fs.writeFile(path, content, function(err) { diff --git a/data/presets/README.md b/data/presets/README.md index 5a2f0c7b6..39c107c2e 100644 --- a/data/presets/README.md +++ b/data/presets/README.md @@ -217,6 +217,7 @@ You can use any of the following open source map icon sets as preset icons. * [Maki](http://www.mapbox.com/maki/) - prefix: `maki-` * [Temaki](http://bhousel.github.io/temaki/docs/) - prefix: `temaki-` +* [Font Awesome](https://fontawesome.com/icons?d=gallery&s=solid) (free, solid only) - prefix: `fa-` When specifying an icon, use the prefixed version of the name, for example `"icon": "maki-park"`. diff --git a/data/presets/presets.json b/data/presets/presets.json index ecf315f67..3c7e6e2ce 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -2021,7 +2021,7 @@ "name": "Monastery Grounds" }, "amenity/motorcycle_parking": { - "icon": "maki-scooter", + "icon": "fa-motorcycle", "fields": [ "capacity", "operator", @@ -6485,7 +6485,7 @@ "matchScore": 0.01 }, "emergency/ambulance_station": { - "icon": "maki-hospital", + "icon": "fa-ambulance", "fields": [ "name", "operator", @@ -18056,7 +18056,7 @@ "name": "Money Lender" }, "shop/motorcycle_repair": { - "icon": "maki-scooter", + "icon": "fa-motorcycle", "fields": [ "name", "operator", @@ -18084,7 +18084,7 @@ "name": "Motorcycle Repair Shop" }, "shop/motorcycle": { - "icon": "maki-scooter", + "icon": "fa-motorcycle", "fields": [ "name", "operator", @@ -83203,7 +83203,7 @@ "shop": "motorcycle" }, "name": "Harley Davidson", - "icon": "maki-scooter", + "icon": "fa-motorcycle", "geometry": [ "point", "area" @@ -83224,7 +83224,7 @@ "shop": "motorcycle" }, "name": "Yamaha", - "icon": "maki-scooter", + "icon": "fa-motorcycle", "geometry": [ "point", "area" diff --git a/data/presets/presets/amenity/motorcycle_parking.json b/data/presets/presets/amenity/motorcycle_parking.json index fc6d8ddcf..2f123b66d 100644 --- a/data/presets/presets/amenity/motorcycle_parking.json +++ b/data/presets/presets/amenity/motorcycle_parking.json @@ -1,5 +1,5 @@ { - "icon": "maki-scooter", + "icon": "fa-motorcycle", "fields": [ "capacity", "operator", diff --git a/data/presets/presets/emergency/ambulance_station.json b/data/presets/presets/emergency/ambulance_station.json index 3cb60c873..3966564ac 100644 --- a/data/presets/presets/emergency/ambulance_station.json +++ b/data/presets/presets/emergency/ambulance_station.json @@ -1,5 +1,5 @@ { - "icon": "maki-hospital", + "icon": "fa-ambulance", "fields": [ "name", "operator", diff --git a/data/presets/presets/shop/motorcycle.json b/data/presets/presets/shop/motorcycle.json index d8c5cd984..0bc76c17e 100644 --- a/data/presets/presets/shop/motorcycle.json +++ b/data/presets/presets/shop/motorcycle.json @@ -1,5 +1,5 @@ { - "icon": "maki-scooter", + "icon": "fa-motorcycle", "fields": [ "name", "operator", diff --git a/data/presets/presets/shop/motorcycle_repair.json b/data/presets/presets/shop/motorcycle_repair.json index 059e86362..ea87b2a59 100644 --- a/data/presets/presets/shop/motorcycle_repair.json +++ b/data/presets/presets/shop/motorcycle_repair.json @@ -1,5 +1,5 @@ { - "icon": "maki-scooter", + "icon": "fa-motorcycle", "fields": [ "name", "operator", diff --git a/data/taginfo.json b/data/taginfo.json index 88759870b..732043e8d 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -924,7 +924,7 @@ "node", "area" ], - "icon_url": "https://raw.githubusercontent.com/mapbox/maki/master/icons/scooter-15.svg?sanitize=true" + "icon_url": "https://raw.githubusercontent.com/openstreetmap/iD/master/svg/fontawesome/motorcycle.svg?sanitize=true" }, { "key": "amenity", @@ -2946,7 +2946,7 @@ "node", "area" ], - "icon_url": "https://raw.githubusercontent.com/mapbox/maki/master/icons/hospital-15.svg?sanitize=true" + "icon_url": "https://raw.githubusercontent.com/openstreetmap/iD/master/svg/fontawesome/ambulance.svg?sanitize=true" }, { "key": "emergency", @@ -7313,7 +7313,7 @@ "node", "area" ], - "icon_url": "https://raw.githubusercontent.com/mapbox/maki/master/icons/scooter-15.svg?sanitize=true" + "icon_url": "https://raw.githubusercontent.com/openstreetmap/iD/master/svg/fontawesome/motorcycle.svg?sanitize=true" }, { "key": "shop", @@ -7323,7 +7323,7 @@ "node", "area" ], - "icon_url": "https://raw.githubusercontent.com/mapbox/maki/master/icons/scooter-15.svg?sanitize=true" + "icon_url": "https://raw.githubusercontent.com/openstreetmap/iD/master/svg/fontawesome/motorcycle.svg?sanitize=true" }, { "key": "shop", diff --git a/modules/svg/defs.js b/modules/svg/defs.js index a13b139b5..a522ac107 100644 --- a/modules/svg/defs.js +++ b/modules/svg/defs.js @@ -122,6 +122,7 @@ export function svgDefs(context) { 'iD-sprite', 'maki-sprite', 'temaki-sprite', + 'fa-sprite', 'community-sprite' ]) .enter() diff --git a/modules/ui/preset_icon.js b/modules/ui/preset_icon.js index 0186f2a11..e19867eee 100644 --- a/modules/ui/preset_icon.js +++ b/modules/ui/preset_icon.js @@ -21,7 +21,7 @@ export function uiPresetIcon() { else if (geom === 'vertex') return p.isFallback() ? '' : 'temaki-vertex'; else - return 'marker-stroked'; + return 'maki-marker-stroked'; } @@ -32,6 +32,8 @@ export function uiPresetIcon() { var picon = getIcon(p, geom); var isMaki = /^maki-/.test(picon); var isTemaki = /^temaki-/.test(picon); + var isFa = /^fa-/.test(picon); + var isPOI = isMaki || isTemaki || isFa; var isFramed = (geom === 'area' || geom === 'vertex'); @@ -83,12 +85,12 @@ export function uiPresetIcon() { icon .attr('class', 'preset-icon preset-icon-' + - ((isMaki || isTemaki) ? (isFramed ? '24' : '28') : (isFramed ? '44' : '60')) + (isPOI ? (isFramed ? '24' : '28') : (isFramed ? '44' : '60')) ); icon.selectAll('svg') .attr('class', function() { - return 'icon ' + picon + (isMaki || isTemaki ? '' : tag_classes(p)); + return 'icon ' + picon + (isPOI ? '' : tag_classes(p)); }); icon.selectAll('use') diff --git a/package.json b/package.json index 631b38e5b..e05ffec53 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "dist:svg:community": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"community-%s\" --symbol-sprite dist/img/community-sprite.svg node_modules/osm-community-index/dist/img/*.svg", "dist:svg:maki": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"maki-%s\" --symbol-sprite dist/img/maki-sprite.svg node_modules/@mapbox/maki/icons/*.svg", "dist:svg:temaki": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"temaki-%s\" --symbol-sprite dist/img/temaki-sprite.svg node_modules/temaki/icons/*.svg", + "dist:svg:fa": "svg-sprite --symbol --symbol-dest . --shape-id-generator \"fa-%s\" --symbol-sprite dist/img/fa-sprite.svg svg/fontawesome/*.svg", "imagery": "node data/update_imagery", "lint": "eslint *.js js/id test/spec modules", "start": "node development_server.js develop", @@ -40,6 +41,8 @@ "wmf-sitematrix": "0.1.4" }, "devDependencies": { + "@fortawesome/fontawesome": "^1.1.5", + "@fortawesome/fontawesome-free-solid": "^5.0.9", "@mapbox/maki": "^4.0.0", "chai": "^4.1.0", "colors": "^1.1.2", diff --git a/svg/fontawesome/ambulance.svg b/svg/fontawesome/ambulance.svg new file mode 100644 index 000000000..ad0bb2d27 --- /dev/null +++ b/svg/fontawesome/ambulance.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/svg/fontawesome/motorcycle.svg b/svg/fontawesome/motorcycle.svg new file mode 100644 index 000000000..531d07e45 --- /dev/null +++ b/svg/fontawesome/motorcycle.svg @@ -0,0 +1 @@ + \ No newline at end of file