diff --git a/README.md b/README.md index 70a0cef23..02ea26062 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# iD - friendly JavaScript editor for [OpenStreetMap](http://www.openstreetmap.org/) +# iD - friendly JavaScript editor for [OpenStreetMap](https://www.openstreetmap.org/) [![Build Status](https://travis-ci.org/openstreetmap/iD.svg?branch=master)](https://travis-ci.org/openstreetmap/iD) [![Greenkeeper badge](https://badges.greenkeeper.io/openstreetmap/iD.svg)](https://greenkeeper.io/) ## Basics -* iD is a JavaScript [OpenStreetMap](http://www.openstreetmap.org/) editor. +* iD is a JavaScript [OpenStreetMap](https://www.openstreetmap.org/) editor. * It's intentionally simple. It lets you do the most basic tasks while not breaking other people's data. * It supports all popular modern desktop browsers: Chrome, Firefox, Safari, @@ -27,9 +27,9 @@ if you're looking for something to do. Come on in, the water's lovely. More help? Ping `jfire` or `bhousel` on: * [OpenStreetMap US Slack](https://osmus-slack.herokuapp.com/) (`#dev` or `#general` channels) -* [OpenStreetMap IRC](http://wiki.openstreetmap.org/wiki/IRC) +* [OpenStreetMap IRC](https://wiki.openstreetmap.org/wiki/IRC) (`irc.oftc.net`, in `#iD` or `#osm-dev` or `#osm`) -* [OpenStreetMap `dev` mailing list](http://wiki.openstreetmap.org/wiki/Mailing_lists) +* [OpenStreetMap `dev` mailing list](https://wiki.openstreetmap.org/wiki/Mailing_lists) ## Prerequisites @@ -50,7 +50,7 @@ To run the current development version of iD on your own computer: #### Cloning the repository -The repository is reasonably large. and it's unlikely that you need the full history. If you are happy to wait for it all to download, run: +The repository is reasonably large, and it's unlikely that you need the full history. If you are happy to wait for it all to download, run: ``` git clone https://github.com/openstreetmap/iD.git @@ -65,6 +65,7 @@ git clone --depth=1 https://github.com/openstreetmap/iD.git If you want to add in the full history later on, perhaps to run `git blame` or `git log`, run `git fetch --depth=1000000` #### Building iD + 1. `cd` into the newly cloned project folder 2. Run `npm install` 3. Run `npm run all` diff --git a/css/60_mapillary.css b/css/60_mapillary.css index 1bf059689..a3067ea7e 100644 --- a/css/60_mapillary.css +++ b/css/60_mapillary.css @@ -73,7 +73,7 @@ background-color: rgba(0, 0, 0, 0.4); padding: 0 4px; border-radius: 4px; - transform: translate(-50%, -120%) !important; + top: -25px; } #mly .domRenderer .Attribution { diff --git a/data/core.yaml b/data/core.yaml index 902a4e78c..82df21868 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -278,6 +278,10 @@ en: title: Background zoom: Zoom vintage: Vintage + source: Source + description: Description + resolution: Resolution + accuracy: Accuracy unknown: Unknown show_tiles: Show Tiles hide_tiles: Hide Tiles diff --git a/data/presets.yaml b/data/presets.yaml index 414dfe937..d58621909 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -630,6 +630,9 @@ en: inscription: # inscription=* label: Inscription + intermittent: + # intermittent=* + label: Intermittent internet_access: # internet_access=* label: Internet Access @@ -1358,6 +1361,9 @@ en: tourism: # tourism=* label: Type + tourism_attraction: + # tourism=* + label: Tourism tower/construction: # 'tower:construction=*' label: Construction @@ -2064,6 +2070,11 @@ en: name: Excrement Bag Vending Machine # 'terms: excrement bags,poop,dog,animal' terms: '' + amenity/vending_machine/feminine_hygiene: + # 'amenity=vending_machine, vending=feminine_hygiene' + name: Feminine Hygiene Vending Machine + # 'terms: condom,tampon,pad,woman,women,menstrual hygiene products,personal care' + terms: '' amenity/vending_machine/news_papers: # 'amenity=vending_machine, vending=news_papers' name: Newspaper Vending Machine @@ -3233,6 +3244,7 @@ en: leisure/bowling_alley: # leisure=bowling_alley name: Bowling Alley + # 'terms: bowling center' terms: '' leisure/common: # leisure=common @@ -3277,6 +3289,11 @@ en: name: Golf Course # 'terms: links' terms: '' + leisure/hackerspace: + # leisure=hackerspace + name: Hackerspace + # 'terms: makerspace,hackspace,hacklab' + terms: '' leisure/horse_riding: # leisure=horse_riding name: Horseback Riding Facility @@ -4891,6 +4908,11 @@ en: name: Stream # 'terms: beck,branch,brook,burn,course,creek,current,drift,flood,flow,freshet,race,rill,rindle,rivulet,run,runnel,rush,spate,spritz,surge,tide,torrent,tributary,watercourse' terms: '' + waterway/stream_intermittent: + # 'waterway=stream, intermittent=yes' + name: Intermittent Stream + # 'terms: arroyo,beck,branch,brook,burn,course,creek,drift,flood,flow,gully,run,runnel,rush,spate,spritz,tributary,wadi,wash,watercourse' + terms: '' waterway/water_point: # waterway=water_point name: Marine Drinking Water diff --git a/data/presets/fields.json b/data/presets/fields.json index 20fa19228..f3dc87ab9 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -875,6 +875,11 @@ "type": "textarea", "label": "Inscription" }, + "intermittent": { + "key": "intermittent", + "type": "check", + "label": "Intermittent" + }, "internet_access": { "key": "internet_access", "type": "combo", @@ -1847,6 +1852,13 @@ "type": "typeCombo", "label": "Type" }, + "tourism_attraction": { + "key": "tourism", + "default": "attraction", + "type": "typeCombo", + "universal": true, + "label": "Tourism" + }, "tourism": { "key": "tourism", "type": "typeCombo", diff --git a/data/presets/fields/intermittent.json b/data/presets/fields/intermittent.json new file mode 100644 index 000000000..ffbe6aaa1 --- /dev/null +++ b/data/presets/fields/intermittent.json @@ -0,0 +1,5 @@ +{ + "key": "intermittent", + "type": "check", + "label": "Intermittent" +} diff --git a/data/presets/fields/tourism_attraction.json b/data/presets/fields/tourism_attraction.json new file mode 100644 index 000000000..2fa8f8f9d --- /dev/null +++ b/data/presets/fields/tourism_attraction.json @@ -0,0 +1,7 @@ +{ + "key": "tourism", + "default": "attraction", + "type": "typeCombo", + "universal": true, + "label": "Tourism" +} diff --git a/data/presets/presets.json b/data/presets/presets.json index 09cc53661..55103bd68 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -889,8 +889,7 @@ ], "geometry": [ "point", - "vertex", - "area" + "vertex" ], "terms": [ "bike", @@ -1088,8 +1087,7 @@ "capacity" ], "geometry": [ - "point", - "area" + "point" ], "tags": { "amenity": "charging_station" @@ -3090,6 +3088,35 @@ }, "name": "Excrement Bag Vending Machine" }, + "amenity/vending_machine/feminine_hygiene": { + "icon": "poi-vending-machine", + "fields": [ + "operator", + "payment_multi", + "currency_multi" + ], + "geometry": [ + "point" + ], + "terms": [ + "condom", + "tampon", + "pad", + "woman", + "women", + "menstrual hygiene products", + "personal care" + ], + "tags": { + "amenity": "vending_machine", + "vending": "feminine_hygiene" + }, + "reference": { + "key": "vending", + "value": "feminine_hygiene" + }, + "name": "Feminine Hygiene Vending Machine" + }, "amenity/vending_machine/newspapers": { "icon": "poi-vending-machine", "fields": [ @@ -3251,8 +3278,7 @@ ], "geometry": [ "point", - "vertex", - "area" + "vertex" ], "tags": { "amenity": "waste_basket" @@ -6428,8 +6454,7 @@ "access" ], "geometry": [ - "line", - "area" + "line" ], "terms": [ "hike", @@ -8379,7 +8404,9 @@ "point", "area" ], - "terms": [], + "terms": [ + "bowling center" + ], "tags": { "leisure": "bowling_alley" }, @@ -8564,6 +8591,29 @@ }, "name": "Golf Course" }, + "leisure/hackerspace": { + "icon": "commercial", + "fields": [ + "name", + "address", + "building_area", + "opening_hours", + "website" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "makerspace", + "hackspace", + "hacklab" + ], + "tags": { + "leisure": "hackerspace" + }, + "name": "Hackerspace" + }, "leisure/horse_riding": { "icon": "horse-riding", "fields": [ @@ -10112,6 +10162,10 @@ }, "natural/spring": { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "point", "vertex" @@ -10189,12 +10243,13 @@ }, "natural/water/lake": { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "area" ], - "fields": [ - "name" - ], "tags": { "natural": "water", "water": "lake" @@ -10212,12 +10267,13 @@ }, "natural/water/pond": { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "area" ], - "fields": [ - "name" - ], "tags": { "natural": "water", "water": "pond" @@ -10237,12 +10293,13 @@ }, "natural/water/reservoir": { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "area" ], - "fields": [ - "name" - ], "tags": { "natural": "water", "water": "reservoir" @@ -15345,7 +15402,8 @@ "icon": "waterway-canal", "fields": [ "name", - "width" + "width", + "intermittent" ], "geometry": [ "line" @@ -15374,7 +15432,8 @@ "waterway/ditch": { "icon": "waterway-ditch", "fields": [ - "tunnel_waterway" + "tunnel_waterway", + "intermittent" ], "geometry": [ "line" @@ -15410,7 +15469,8 @@ "waterway/drain": { "icon": "waterway-stream", "fields": [ - "tunnel_waterway" + "tunnel_waterway", + "intermittent" ], "geometry": [ "line" @@ -15449,7 +15509,8 @@ "fields": [ "name", "tunnel_waterway", - "width" + "width", + "intermittent" ], "geometry": [ "line" @@ -15515,12 +15576,56 @@ }, "name": "Marine Toilet Disposal" }, + "waterway/stream_intermittent": { + "icon": "waterway-stream", + "fields": [ + "name", + "tunnel_waterway", + "width", + "intermittent" + ], + "geometry": [ + "line" + ], + "terms": [ + "arroyo", + "beck", + "branch", + "brook", + "burn", + "course", + "creek", + "drift", + "flood", + "flow", + "gully", + "run", + "runnel", + "rush", + "spate", + "spritz", + "tributary", + "wadi", + "wash", + "watercourse" + ], + "tags": { + "waterway": "stream", + "intermittent": "yes" + }, + "reference": { + "key": "waterway", + "value": "stream" + }, + "name": "Intermittent Stream" + }, "waterway/stream": { "icon": "waterway-stream", "fields": [ "name", "tunnel_waterway", - "width" + "width", + "intermittent" ], "geometry": [ "line" @@ -15574,7 +15679,8 @@ "fields": [ "name", "height", - "width" + "width", + "intermittent" ], "geometry": [ "vertex" diff --git a/data/presets/presets/amenity/bicycle_repair_station.json b/data/presets/presets/amenity/bicycle_repair_station.json index 717affe47..11dfd6d78 100644 --- a/data/presets/presets/amenity/bicycle_repair_station.json +++ b/data/presets/presets/amenity/bicycle_repair_station.json @@ -9,8 +9,7 @@ ], "geometry": [ "point", - "vertex", - "area" + "vertex" ], "terms": [ "bike", diff --git a/data/presets/presets/amenity/charging_station.json b/data/presets/presets/amenity/charging_station.json index 481fb0573..dfdb65ffe 100644 --- a/data/presets/presets/amenity/charging_station.json +++ b/data/presets/presets/amenity/charging_station.json @@ -5,8 +5,7 @@ "capacity" ], "geometry": [ - "point", - "area" + "point" ], "tags": { "amenity": "charging_station" diff --git a/data/presets/presets/amenity/vending_machine/feminine_hygiene.json b/data/presets/presets/amenity/vending_machine/feminine_hygiene.json new file mode 100644 index 000000000..f76acb7b6 --- /dev/null +++ b/data/presets/presets/amenity/vending_machine/feminine_hygiene.json @@ -0,0 +1,29 @@ +{ + "icon": "poi-vending-machine", + "fields": [ + "operator", + "payment_multi", + "currency_multi" + ], + "geometry": [ + "point" + ], + "terms": [ + "condom", + "tampon", + "pad", + "woman", + "women", + "menstrual hygiene products", + "personal care" + ], + "tags": { + "amenity": "vending_machine", + "vending": "feminine_hygiene" + }, + "reference": { + "key": "vending", + "value": "feminine_hygiene" + }, + "name": "Feminine Hygiene Vending Machine" +} diff --git a/data/presets/presets/amenity/waste_basket.json b/data/presets/presets/amenity/waste_basket.json index a3fa447f6..302c343a6 100644 --- a/data/presets/presets/amenity/waste_basket.json +++ b/data/presets/presets/amenity/waste_basket.json @@ -5,8 +5,7 @@ ], "geometry": [ "point", - "vertex", - "area" + "vertex" ], "tags": { "amenity": "waste_basket" diff --git a/data/presets/presets/highway/footway.json b/data/presets/presets/highway/footway.json index 613d00b7a..1e26b1d7f 100644 --- a/data/presets/presets/highway/footway.json +++ b/data/presets/presets/highway/footway.json @@ -9,8 +9,7 @@ "access" ], "geometry": [ - "line", - "area" + "line" ], "terms": [ "hike", diff --git a/data/presets/presets/leisure/bowling_alley.json b/data/presets/presets/leisure/bowling_alley.json index 63ca737c2..ce6938a3a 100644 --- a/data/presets/presets/leisure/bowling_alley.json +++ b/data/presets/presets/leisure/bowling_alley.json @@ -13,6 +13,7 @@ "area" ], "terms": [ + "bowling center" ], "tags": { "leisure": "bowling_alley" diff --git a/data/presets/presets/leisure/hackerspace.json b/data/presets/presets/leisure/hackerspace.json new file mode 100644 index 000000000..fdffb2725 --- /dev/null +++ b/data/presets/presets/leisure/hackerspace.json @@ -0,0 +1,23 @@ +{ + "icon": "commercial", + "fields": [ + "name", + "address", + "building_area", + "opening_hours", + "website" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "makerspace", + "hackspace", + "hacklab" + ], + "tags": { + "leisure": "hackerspace" + }, + "name": "Hackerspace" +} diff --git a/data/presets/presets/natural/spring.json b/data/presets/presets/natural/spring.json index 447110579..2292d40a0 100644 --- a/data/presets/presets/natural/spring.json +++ b/data/presets/presets/natural/spring.json @@ -1,5 +1,9 @@ { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "point", "vertex" diff --git a/data/presets/presets/natural/water/lake.json b/data/presets/presets/natural/water/lake.json index a35c5c50e..ea3bf28c0 100644 --- a/data/presets/presets/natural/water/lake.json +++ b/data/presets/presets/natural/water/lake.json @@ -1,11 +1,12 @@ { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "area" ], - "fields": [ - "name" - ], "tags": { "natural": "water", "water": "lake" diff --git a/data/presets/presets/natural/water/pond.json b/data/presets/presets/natural/water/pond.json index ef6aeaf08..21d246f5d 100644 --- a/data/presets/presets/natural/water/pond.json +++ b/data/presets/presets/natural/water/pond.json @@ -1,11 +1,12 @@ { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "area" ], - "fields": [ - "name" - ], "tags": { "natural": "water", "water": "pond" diff --git a/data/presets/presets/natural/water/reservoir.json b/data/presets/presets/natural/water/reservoir.json index 6fba02ecb..274c721e2 100644 --- a/data/presets/presets/natural/water/reservoir.json +++ b/data/presets/presets/natural/water/reservoir.json @@ -1,11 +1,12 @@ { "icon": "water", + "fields": [ + "name", + "intermittent" + ], "geometry": [ "area" ], - "fields": [ - "name" - ], "tags": { "natural": "water", "water": "reservoir" diff --git a/data/presets/presets/waterway/boatyard.json b/data/presets/presets/waterway/boatyard.json index 919354792..a445dd6d7 100644 --- a/data/presets/presets/waterway/boatyard.json +++ b/data/presets/presets/waterway/boatyard.json @@ -1,7 +1,7 @@ { "icon": "harbor", "fields":[ - "name", + "name", "operator" ], "geometry": [ diff --git a/data/presets/presets/waterway/canal.json b/data/presets/presets/waterway/canal.json index 6cab011d8..2b22b0f57 100644 --- a/data/presets/presets/waterway/canal.json +++ b/data/presets/presets/waterway/canal.json @@ -2,7 +2,8 @@ "icon": "waterway-canal", "fields": [ "name", - "width" + "width", + "intermittent" ], "geometry": [ "line" diff --git a/data/presets/presets/waterway/ditch.json b/data/presets/presets/waterway/ditch.json index 9373764b1..e5ff49863 100644 --- a/data/presets/presets/waterway/ditch.json +++ b/data/presets/presets/waterway/ditch.json @@ -1,7 +1,8 @@ { "icon": "waterway-ditch", "fields": [ - "tunnel_waterway" + "tunnel_waterway", + "intermittent" ], "geometry": [ "line" diff --git a/data/presets/presets/waterway/drain.json b/data/presets/presets/waterway/drain.json index 71f63d634..614284713 100644 --- a/data/presets/presets/waterway/drain.json +++ b/data/presets/presets/waterway/drain.json @@ -1,7 +1,8 @@ { "icon": "waterway-stream", "fields": [ - "tunnel_waterway" + "tunnel_waterway", + "intermittent" ], "geometry": [ "line" diff --git a/data/presets/presets/waterway/river.json b/data/presets/presets/waterway/river.json index 3125600e2..a4448bf67 100644 --- a/data/presets/presets/waterway/river.json +++ b/data/presets/presets/waterway/river.json @@ -3,7 +3,8 @@ "fields": [ "name", "tunnel_waterway", - "width" + "width", + "intermittent" ], "geometry": [ "line" diff --git a/data/presets/presets/waterway/stream.json b/data/presets/presets/waterway/stream.json index 802924646..3b26a9436 100644 --- a/data/presets/presets/waterway/stream.json +++ b/data/presets/presets/waterway/stream.json @@ -3,7 +3,8 @@ "fields": [ "name", "tunnel_waterway", - "width" + "width", + "intermittent" ], "geometry": [ "line" diff --git a/data/presets/presets/waterway/stream_intermittent.json b/data/presets/presets/waterway/stream_intermittent.json new file mode 100644 index 000000000..e6ff48837 --- /dev/null +++ b/data/presets/presets/waterway/stream_intermittent.json @@ -0,0 +1,43 @@ +{ + "icon": "waterway-stream", + "fields": [ + "name", + "tunnel_waterway", + "width", + "intermittent" + ], + "geometry": [ + "line" + ], + "terms": [ + "arroyo", + "beck", + "branch", + "brook", + "burn", + "course", + "creek", + "drift", + "flood", + "flow", + "gully", + "run", + "runnel", + "rush", + "spate", + "spritz", + "tributary", + "wadi", + "wash", + "watercourse" + ], + "tags": { + "waterway": "stream", + "intermittent": "yes" + }, + "reference": { + "key": "waterway", + "value": "stream" + }, + "name": "Intermittent Stream" +} diff --git a/data/presets/presets/waterway/waterfall.json b/data/presets/presets/waterway/waterfall.json index 2081e6a37..d87e625dc 100644 --- a/data/presets/presets/waterway/waterfall.json +++ b/data/presets/presets/waterway/waterfall.json @@ -3,7 +3,8 @@ "fields": [ "name", "height", - "width" + "width", + "intermittent" ], "geometry": [ "vertex" diff --git a/data/taginfo.json b/data/taginfo.json index b7bc2ea30..a8cc6d1e5 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -555,6 +555,10 @@ "key": "vending", "value": "excrement_bags" }, + { + "key": "vending", + "value": "feminine_hygiene" + }, { "key": "vending", "value": "newspapers" @@ -1645,6 +1649,10 @@ "key": "leisure", "value": "golf_course" }, + { + "key": "leisure", + "value": "hackerspace" + }, { "key": "leisure", "value": "horse_riding" @@ -3065,6 +3073,10 @@ "key": "waterway", "value": "sanitary_dump_station" }, + { + "key": "intermittent", + "value": "yes" + }, { "key": "waterway", "value": "stream" diff --git a/dist/locales/en.json b/dist/locales/en.json index 162cf2f74..ca6584498 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -354,6 +354,10 @@ "title": "Background", "zoom": "Zoom", "vintage": "Vintage", + "source": "Source", + "description": "Description", + "resolution": "Resolution", + "accuracy": "Accuracy", "unknown": "Unknown", "show_tiles": "Show Tiles", "hide_tiles": "Hide Tiles" @@ -1619,6 +1623,9 @@ "inscription": { "label": "Inscription" }, + "intermittent": { + "label": "Intermittent" + }, "internet_access": { "label": "Internet Access", "options": { @@ -2213,6 +2220,9 @@ "tomb": { "label": "Type" }, + "tourism_attraction": { + "label": "Tourism" + }, "tourism": { "label": "Type" }, @@ -2855,6 +2865,10 @@ "name": "Excrement Bag Vending Machine", "terms": "excrement bags,poop,dog,animal" }, + "amenity/vending_machine/feminine_hygiene": { + "name": "Feminine Hygiene Vending Machine", + "terms": "condom,tampon,pad,woman,women,menstrual hygiene products,personal care" + }, "amenity/vending_machine/newspapers": { "name": "Newspaper Vending Machine", "terms": "newspaper" @@ -3917,7 +3931,7 @@ }, "leisure/bowling_alley": { "name": "Bowling Alley", - "terms": "" + "terms": "bowling center" }, "leisure/common": { "name": "Common", @@ -3955,6 +3969,10 @@ "name": "Golf Course", "terms": "links" }, + "leisure/hackerspace": { + "name": "Hackerspace", + "terms": "makerspace,hackspace,hacklab" + }, "leisure/horse_riding": { "name": "Horseback Riding Facility", "terms": "equestrian,stable" @@ -5399,6 +5417,10 @@ "name": "Marine Toilet Disposal", "terms": "Boat,Watercraft,Sanitary,Dump Station,Pumpout,Pump out,Elsan,CDP,CTDP,Chemical Toilet" }, + "waterway/stream_intermittent": { + "name": "Intermittent Stream", + "terms": "arroyo,beck,branch,brook,burn,course,creek,drift,flood,flow,gully,run,runnel,rush,spate,spritz,tributary,wadi,wash,watercourse" + }, "waterway/stream": { "name": "Stream", "terms": "beck,branch,brook,burn,course,creek,current,drift,flood,flow,freshet,race,rill,rindle,rivulet,run,runnel,rush,spate,spritz,surge,tide,torrent,tributary,watercourse" diff --git a/modules/presets/collection.js b/modules/presets/collection.js index 35e2b023b..e4b48939f 100644 --- a/modules/presets/collection.js +++ b/modules/presets/collection.js @@ -56,13 +56,23 @@ export function presetCollection(collection) { var leading_name = _.filter(searchable, function(a) { return leading(a.name().toLowerCase()); }).sort(function(a, b) { - var i; + var aCompare = a.name().toLowerCase(), + bCompare = b.name().toLowerCase(), + i; + + // priority if search string matches preset name exactly - #4325 + if (value === aCompare) return -1; + if (value === bCompare) return 1; + + // priority for higher matchScore i = b.originalScore - a.originalScore; if (i !== 0) return i; - i = a.name().toLowerCase().indexOf(value) - b.name().toLowerCase().indexOf(value); + // priority if search string appears earlier in preset name + i = aCompare.indexOf(value) - bCompare.indexOf(value); if (i !== 0) return i; + // priority for shorter preset names return a.name().length - b.name().length; }); diff --git a/modules/presets/preset.js b/modules/presets/preset.js index a011e1b61..e9c2c6943 100644 --- a/modules/presets/preset.js +++ b/modules/presets/preset.js @@ -47,19 +47,19 @@ export function presetPreset(id, preset, fields) { }; - var name = preset.name || ''; + var origName = preset.name || ''; preset.name = function() { if (preset.suggestion) { id = id.split('/'); id = id[0] + '/' + id[1]; - return name + ' - ' + t('presets.presets.' + id + '.name'); + return origName + ' - ' + t('presets.presets.' + id + '.name'); } - return preset.t('name', {'default': name}); + return preset.t('name', { 'default': origName }); }; - + var origTerms = (preset.terms || []).join(); preset.terms = function() { - return preset.t('terms', {'default': ''}).toLowerCase().trim().split(/\s*,+\s*/); + return preset.t('terms', { 'default': origTerms }).toLowerCase().trim().split(/\s*,+\s*/); }; diff --git a/modules/renderer/background.js b/modules/renderer/background.js index 5fc5286de..553047532 100644 --- a/modules/renderer/background.js +++ b/modules/renderer/background.js @@ -243,6 +243,8 @@ export function rendererBackground(context) { backgroundSources = dataImagery.map(function(source) { if (source.type === 'bing') { return rendererBackgroundSource.Bing(source, dispatch); + } else if (source.id === 'EsriWorldImagery') { + return rendererBackgroundSource.Esri(source); } else { return rendererBackgroundSource(source); } diff --git a/modules/renderer/background_source.js b/modules/renderer/background_source.js index de961de20..5fbbfca8a 100644 --- a/modules/renderer/background_source.js +++ b/modules/renderer/background_source.js @@ -133,13 +133,15 @@ export function rendererBackgroundSource(data) { source.copyrightNotices = function() {}; - source.getVintage = function(center, tileCoord, callback) { + source.getMetadata = function(center, tileCoord, callback) { var vintage = { start: localeDateString(source.startDate), end: localeDateString(source.endDate) }; vintage.range = vintageRange(vintage); - callback(null, vintage); + + var metadata = { vintage: vintage }; + callback(null, metadata); }; @@ -158,6 +160,7 @@ rendererBackgroundSource.Bing = function(data, dispatch) { url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial?include=ImageryProviders&key=' + key + '&jsonp={callback}', cache = {}, + inflight = {}, providers = []; jsonpRequest(url, function(json) { @@ -190,21 +193,26 @@ rendererBackgroundSource.Bing = function(data, dispatch) { }; - bing.getVintage = function(center, tileCoord, callback) { + bing.getMetadata = function(center, tileCoord, callback) { var tileId = tileCoord.slice(0, 3).join('/'), zoom = Math.min(tileCoord[2], 21), centerPoint = center[1] + ',' + center[0], // lat,lng url = 'https://dev.virtualearth.net/REST/v1/Imagery/Metadata/Aerial/' + centerPoint + '?zl=' + zoom + '&key=' + key + '&jsonp={callback}'; + if (inflight[tileId]) return; + if (!cache[tileId]) { cache[tileId] = {}; } - if (cache[tileId] && cache[tileId].vintage) { - return callback(null, cache[tileId].vintage); + if (cache[tileId] && cache[tileId].metadata) { + return callback(null, cache[tileId].metadata); } + inflight[tileId] = true; jsonpRequest(url, function(result) { + delete inflight[tileId]; + var err = (!result && 'Unknown Error') || result.errorDetails; if (err) { return callback(err); @@ -214,8 +222,10 @@ rendererBackgroundSource.Bing = function(data, dispatch) { end: localeDateString(result.resourceSets[0].resources[0].vintageEnd) }; vintage.range = vintageRange(vintage); - cache[tileId].vintage = vintage; - return callback(null, vintage); + + var metadata = { vintage: vintage }; + cache[tileId].metadata = metadata; + return callback(null, metadata); } }); }; @@ -228,6 +238,127 @@ rendererBackgroundSource.Bing = function(data, dispatch) { }; + +rendererBackgroundSource.Esri = function(data) { + + // don't request blank tiles, instead overzoom real tiles - #4327 + // deprecated technique, but it works (for now) + if (data.template.match(/blankTile/) === null) { + data.template = data.template + '?blankTile=false'; + } + + var esri = rendererBackgroundSource(data), + cache = {}, + inflight = {}; + + esri.getMetadata = function(center, tileCoord, callback) { + var tileId = tileCoord.slice(0, 3).join('/'), + zoom = Math.min(tileCoord[2], esri.scaleExtent[1]), + centerPoint = center[0] + ',' + center[1], // long, lat (as it should be) + unknown = t('info_panels.background.unknown'), + metadataLayer, + vintage = {}, + metadata = {}; + + if (inflight[tileId]) return; + + switch (true) { + case zoom >= 19: + metadataLayer = 3; + break; + case zoom >= 17: + metadataLayer = 2; + break; + case zoom >= 13: + metadataLayer = 0; + break; + default: + metadataLayer = 99; + } + + // build up query using the layer appropriate to the current zoom + var url = 'https://services.arcgisonline.com/arcgis/rest/services/World_Imagery/MapServer/' + metadataLayer + '/query?returnGeometry=false&geometry=' + centerPoint + '&inSR=4326&geometryType=esriGeometryPoint&outFields=*&f=json&callback={callback}'; + + if (!cache[tileId]) { + cache[tileId] = {}; + } + if (cache[tileId] && cache[tileId].metadata) { + return callback(null, cache[tileId].metadata); + } + + // accurate metadata is only available >= 13 + if (metadataLayer === 99) { + vintage = { + start: null, + end: null, + range: null + }; + metadata = { + vintage: null, + source: unknown, + description: unknown, + resolution: unknown, + accuracy: unknown + }; + + callback(null, metadata); + + } else { + inflight[tileId] = true; + jsonpRequest(url, function(result) { + delete inflight[tileId]; + + var err; + if (!result) { + err = 'Unknown Error'; + } else if (result.features && result.features.length < 1) { + err = 'No Results'; + } else if (result.error && result.error.message) { + err = result.error.message; + } + + if (err) { + return callback(err); + } else { + // pass through the discrete capture date from metadata + var captureDate = localeDateString(result.features[0].attributes.SRC_DATE2); + vintage = { + start: captureDate, + end: captureDate, + range: captureDate + }; + metadata = { + vintage: vintage, + source: clean(result.features[0].attributes.NICE_NAME), + description: clean(result.features[0].attributes.NICE_DESC), + resolution: clean(result.features[0].attributes.SRC_RES), + accuracy: clean(result.features[0].attributes.SRC_ACC) + }; + + // append units - meters + if (isFinite(metadata.resolution)) { + metadata.resolution += ' m'; + } + if (isFinite(metadata.accuracy)) { + metadata.accuracy += ' m'; + } + + cache[tileId].metadata = metadata; + return callback(null, metadata); + } + }); + } + + + function clean(val) { + return String(val).trim() || unknown; + } + }; + + return esri; +}; + + rendererBackgroundSource.None = function() { var source = rendererBackgroundSource({ id: 'none', template: '' }); diff --git a/modules/renderer/tile_layer.js b/modules/renderer/tile_layer.js index 4a1735fa4..88f2d9141 100644 --- a/modules/renderer/tile_layer.js +++ b/modules/renderer/tile_layer.js @@ -255,8 +255,8 @@ export function rendererTileLayer(context) { .each(function(d) { var span = d3.select(this); var center = context.projection.invert(tileCenter(d)); - source.getVintage(center, d, function(err, result) { - span.text((result && result.range) || + source.getMetadata(center, d, function(err, result) { + span.text((result && result.vintage && result.vintage.range) || t('info_panels.background.vintage') + ': ' + t('info_panels.background.unknown') ); }); diff --git a/modules/svg/tag_classes.js b/modules/svg/tag_classes.js index be2d6720c..d7bfa43af 100644 --- a/modules/svg/tag_classes.js +++ b/modules/svg/tag_classes.js @@ -10,7 +10,7 @@ export function svgTagClasses() { ], statuses = [ 'proposed', 'construction', 'disused', 'abandoned', 'dismantled', - 'razed', 'demolished', 'obliterated' + 'razed', 'demolished', 'obliterated', 'intermittent' ], secondaries = [ 'oneway', 'bridge', 'tunnel', 'embankment', 'cutting', 'barrier', diff --git a/modules/ui/commit.js b/modules/ui/commit.js index 9582a8386..0492e3a37 100644 --- a/modules/ui/commit.js +++ b/modules/ui/commit.js @@ -40,29 +40,31 @@ export function uiCommit(context) { var osm = context.connection(); if (!osm) return; - var comment = context.storage('comment') || '', - commentDate = +context.storage('commentDate') || 0, - hashtags = context.storage('hashtags'), + // expire stored comment and hashtags after cutoff datetime - #3947 + var commentDate = +context.storage('commentDate') || 0, currDate = Date.now(), cutoff = 2 * 86400 * 1000; // 2 days - - // expire stored comment and hashtags after cutoff datetime - #3947 if (commentDate > currDate || currDate - commentDate > cutoff) { - comment = ''; - hashtags = undefined; + context.storage('comment', null); + context.storage('hashtags', null); } var tags; if (!changeset) { var detected = utilDetect(); tags = { - comment: comment, + comment: context.storage('comment') || '', created_by: ('iD ' + context.version).substr(0, 255), imagery_used: context.history().imageryUsed().join(';').substr(0, 255), host: detected.host.substr(0, 255), locale: detected.locale.substr(0, 255) }; + // call findHashtags initially - this will remove stored + // hashtags if any hashtags are found in the comment - #4304 + findHashtags(tags, true); + + var hashtags = context.storage('hashtags'); if (hashtags) { tags.hashtags = hashtags; } @@ -276,14 +278,23 @@ export function uiCommit(context) { } - function findHashtags(tags) { - return _.unionBy(commentTags(), hashTags(), function (s) { + function findHashtags(tags, commentOnly) { + var inComment = commentTags(), + inHashTags = hashTags(); + + if (inComment !== null) { // when hashtags are detected in comment... + context.storage('hashtags', null); // always remove stored hashtags - #4304 + if (commentOnly) { inHashTags = null; } // optionally override hashtags field + } + return _.unionBy(inComment, inHashTags, function (s) { return s.toLowerCase(); }); // Extract hashtags from `comment` function commentTags() { - return tags.comment.match(/#[^\s\#]+/g); + return tags.comment + .replace(/http\S*/g, '') // drop anything that looks like a URL - #4289 + .match(/#[\w-]+/g); } // Extract and clean hashtags from `hashtags` @@ -293,7 +304,7 @@ export function uiCommit(context) { .split(/[,;\s]+/) .map(function (s) { if (s[0] !== '#') { s = '#' + s; } // prepend '#' - var matched = s.match(/#[^\s\#]+/g); // match valid hashtags + var matched = s.match(/#[\w-]+/g); // match valid hashtags return matched && matched[0]; }).filter(Boolean); // exclude falsey } @@ -327,7 +338,9 @@ export function uiCommit(context) { }); if (!onInput) { - var arr = findHashtags(tags); + // when changing the comment, override hashtags with any found in comment. + var commentOnly = changed.hasOwnProperty('comment') && (changed.comment !== ''); + var arr = findHashtags(tags, commentOnly); if (arr.length) { tags.hashtags = arr.join(';').substr(0, 255); context.storage('hashtags', tags.hashtags); diff --git a/modules/ui/field.js b/modules/ui/field.js index ee70f98c5..d2c34ebd8 100644 --- a/modules/ui/field.js +++ b/modules/ui/field.js @@ -18,6 +18,7 @@ export function uiField(context, presetField, entity, options) { var dispatch = d3.dispatch('change'), field = _.clone(presetField), + show = options.show, state = '', tags = {}; @@ -33,8 +34,6 @@ export function uiField(context, presetField, entity, options) { field.keys = field.keys || [field.key]; - field.show = options.show; - function isModified() { if (!entity) return false; @@ -176,8 +175,18 @@ export function uiField(context, presetField, entity, options) { }; + field.show = function() { + show = true; + if (field.default && field.key && tags[field.key] !== field.default) { + var t = {}; + t[field.key] = field.default; + dispatch.call('change', this, t); + } + }; + + field.isShown = function() { - return field.show || _.some(field.keys, function(key) { return !!tags[key]; }); + return show || _.some(field.keys, function(key) { return !!tags[key]; }); }; diff --git a/modules/ui/form_fields.js b/modules/ui/form_fields.js index bcb234558..25876376b 100644 --- a/modules/ui/form_fields.js +++ b/modules/ui/form_fields.js @@ -101,7 +101,7 @@ export function uiFormFields(context) { .minItems(1) .on('accept', function (d) { var field = d.field; - field.show = true; + field.show(); render(selection); if (field.type !== 'semiCombo' && field.type !== 'multiCombo') { field.focus(); diff --git a/modules/ui/panels/background.js b/modules/ui/panels/background.js index 44725af1b..a17146d9f 100644 --- a/modules/ui/panels/background.js +++ b/modules/ui/panels/background.js @@ -5,16 +5,18 @@ import { t } from '../../util/locale'; export function uiPanelBackground(context) { var background = context.background(); - var currSource = null; - var currZoom = ''; - var currVintage = ''; + var currSourceName = null; + var metadata = {}; + var metadataKeys = [ + 'zoom', 'vintage', 'source', 'description', 'resolution', 'accuracy' + ]; + var debouncedRedraw = _.debounce(redraw, 250); function redraw(selection) { - if (currSource !== background.baseLayerSource().name()) { - currSource = background.baseLayerSource().name(); - currZoom = ''; - currVintage = ''; + if (currSourceName !== background.baseLayerSource().name()) { + currSourceName = background.baseLayerSource().name(); + metadata = {}; } selection.html(''); @@ -25,25 +27,20 @@ export function uiPanelBackground(context) { list .append('li') - .text(currSource); + .text(currSourceName); - list - .append('li') - .text(t('info_panels.background.zoom') + ': ') - .append('span') - .attr('class', 'zoom') - .text(currZoom); + metadataKeys.forEach(function(k) { + list + .append('li') + .attr('class', 'background-info-list-' + k) + .classed('hide', !metadata[k]) + .text(t('info_panels.background.' + k) + ': ') + .append('span') + .attr('class', 'background-info-span-' + k) + .text(metadata[k]); + }); - list - .append('li') - .text(t('info_panels.background.vintage') + ': ') - .append('span') - .attr('class', 'vintage') - .text(currVintage); - - if (!currVintage) { - debouncedGetVintage(selection); - } + debouncedGetMetadata(selection); var toggle = context.getDebug('tile') ? 'hide_tiles' : 'show_tiles'; @@ -60,24 +57,47 @@ export function uiPanelBackground(context) { } - var debouncedGetVintage = _.debounce(getVintage, 250); - function getVintage(selection) { + var debouncedGetMetadata = _.debounce(getMetadata, 250); + + function getMetadata(selection) { var tile = d3.select('.layer-background img.tile-center'); // tile near viewport center if (tile.empty()) return; - var d = tile.datum(), + var sourceName = currSourceName, + d = tile.datum(), zoom = (d && d.length >= 3 && d[2]) || Math.floor(context.map().zoom()), center = context.map().center(); - currZoom = String(zoom); - selection.selectAll('.zoom') - .text(currZoom); + // update zoom + metadata.zoom = String(zoom); + selection.selectAll('.background-info-list-zoom') + .classed('hide', false) + .selectAll('.background-info-span-zoom') + .text(metadata.zoom); if (!d || !d.length >= 3) return; - background.baseLayerSource().getVintage(center, d, function(err, result) { - currVintage = (result && result.range) || t('info_panels.background.unknown'); - selection.selectAll('.vintage') - .text(currVintage); + + background.baseLayerSource().getMetadata(center, d, function(err, result) { + if (err || currSourceName !== sourceName) return; + + // update vintage + var vintage = result.vintage; + metadata.vintage = (vintage && vintage.range) || t('info_panels.background.unknown'); + selection.selectAll('.background-info-list-vintage') + .classed('hide', false) + .selectAll('.background-info-span-vintage') + .text(metadata.vintage); + + // update other metdata + _.without(metadataKeys, 'zoom', 'vintage') + .forEach(function(k) { + var val = result[k]; + metadata[k] = val; + selection.selectAll('.background-info-list-' + k) + .classed('hide', !val) + .selectAll('.background-info-span-' + k) + .text(val); + }); }); } @@ -87,10 +107,10 @@ export function uiPanelBackground(context) { context.map() .on('drawn.info-background', function() { - selection.call(redraw); + selection.call(debouncedRedraw); }) .on('move.info-background', function() { - selection.call(debouncedGetVintage); + selection.call(debouncedGetMetadata); }); }; diff --git a/modules/ui/status.js b/modules/ui/status.js index d9ba2bd0e..9d9dbc3e2 100644 --- a/modules/ui/status.js +++ b/modules/ui/status.js @@ -29,6 +29,7 @@ export function uiStatus(context) { osm.authenticate(); }); } else { + // eslint-disable-next-line no-warning-comments // TODO: nice messages for different error types selection.text(t('status.error')); } diff --git a/test/spec/presets/collection.js b/test/spec/presets/collection.js index e88912329..45af5e4c3 100644 --- a/test/spec/presets/collection.js +++ b/test/spec/presets/collection.js @@ -49,7 +49,14 @@ describe('iD.presetCollection', function() { name: 'Park', tags: { leisure: 'park' }, geometry: ['point', 'area'], - terms: [ 'grass' ] + terms: [ 'grass' ], + matchScore: 0.5 + }), + parking: iD.presetPreset('__test/amenity/parking', { + name: 'Parking', + tags: { amenity: 'parking' }, + geometry: ['point', 'area'], + terms: [ 'cars' ] }), soccer: iD.presetPreset('__test/leisure/pitch/soccer', { name: 'Soccer Field', @@ -68,7 +75,7 @@ describe('iD.presetCollection', function() { var c = iD.presetCollection([ p.point, p.line, p.area, p.grill, p.sandpit, p.residential, - p.grass1, p.grass2, p.park, p.soccer, p.football + p.grass1, p.grass2, p.park, p.parking, p.soccer, p.football ]); describe('#item', function() { @@ -93,30 +100,42 @@ describe('iD.presetCollection', function() { it('returns alternate matches in correct order', function() { var col = c.search('gri', 'point').matchGeometry('point').collection; - expect(col.indexOf(p.grill)).to.eql(0); // 1. 'Grill' (leading name) - expect(col.indexOf(p.football)).to.eql(7); // 2. 'Football' (leading term 'gridiron') - expect(col.indexOf(p.sandpit)).to.eql(1); // 3. 'Sandpit' (leading tag value 'grit_bin') - expect(col.indexOf(p.grass1)).to.be.within(2,3); // 4. 'Grass' (similar name) - expect(col.indexOf(p.grass2)).to.be.within(3,4); // 5. 'Ğṝȁß' (similar name) - expect(col.indexOf(p.park)).to.eql(4); // 6. 'Park' (similar term 'grass') + expect(col.indexOf(p.grill), 'Grill').to.eql(0); // 1. 'Grill' (leading name) + expect(col.indexOf(p.football), 'Football').to.eql(1); // 2. 'Football' (leading term 'gridiron') + expect(col.indexOf(p.sandpit), 'Sandpit').to.eql(2); // 3. 'Sandpit' (leading tag value 'grit_bin') + expect(col.indexOf(p.grass1), 'Grass').to.be.within(3,4); // 4. 'Grass' (similar name) + expect(col.indexOf(p.grass2), 'Ğṝȁß').to.be.within(3,4); // 5. 'Ğṝȁß' (similar name) + expect(col.indexOf(p.park), 'Park').to.eql(5); // 6. 'Park' (similar term 'grass') + }); + + it('sorts preset with matchScore penalty below others', function() { + var col = c.search('par', 'point').matchGeometry('point').collection; + expect(col.indexOf(p.parking), 'Parking').to.eql(0); // 1. 'Parking' (default matchScore) + expect(col.indexOf(p.park), 'Park').to.eql(1); // 2. 'Park' (low matchScore) + }); + + it('ignores matchScore penalty for exact name match', function() { + var col = c.search('park', 'point').matchGeometry('point').collection; + expect(col.indexOf(p.park), 'Park').to.eql(0); // 1. 'Park' (low matchScore) + expect(col.indexOf(p.parking), 'Parking').to.eql(1); // 2. 'Parking' (default matchScore) }); it('considers diacritics on exact matches', function() { var col = c.search('ğṝȁ', 'point').matchGeometry('point').collection; - expect(col.indexOf(p.grass2)).to.eql(0); // 1. 'Ğṝȁß' (leading name) - expect(col.indexOf(p.grass1)).to.eql(1); // 2. 'Grass' (similar name) + expect(col.indexOf(p.grass2), 'Ğṝȁß').to.eql(0); // 1. 'Ğṝȁß' (leading name) + expect(col.indexOf(p.grass1), 'Grass').to.eql(1); // 2. 'Grass' (similar name) }); it('replaces diacritics on fuzzy matches', function() { var col = c.search('graß', 'point').matchGeometry('point').collection; - expect(col.indexOf(p.grass1)).to.be.within(0,1); // 1. 'Grass' (similar name) - expect(col.indexOf(p.grass2)).to.be.within(0,1); // 2. 'Ğṝȁß' (similar name) + expect(col.indexOf(p.grass1), 'Grass').to.be.within(0,1); // 1. 'Grass' (similar name) + expect(col.indexOf(p.grass2), 'Ğṝȁß').to.be.within(0,1); // 2. 'Ğṝȁß' (similar name) }); it('includes the appropriate fallback preset', function() { - expect(c.search('foo', 'point').collection).to.include(p.point); - expect(c.search('foo', 'line').collection).to.include(p.line); - expect(c.search('foo', 'area').collection).to.include(p.area); + expect(c.search('foo', 'point').collection, 'point').to.include(p.point); + expect(c.search('foo', 'line').collection, 'line').to.include(p.line); + expect(c.search('foo', 'area').collection, 'area').to.include(p.area); }); it('excludes presets with searchable: false', function() { diff --git a/test/spec/spec_helpers.js b/test/spec/spec_helpers.js index 50fba97a4..083969397 100644 --- a/test/spec/spec_helpers.js +++ b/test/spec/spec_helpers.js @@ -20,4 +20,5 @@ mocha.setup({ }); expect = chai.expect; -var d3 = iD.d3; +// eslint-disable-next-line no-unused-vars +var d3 = iD.d3; \ No newline at end of file