diff --git a/css/app.css b/css/app.css index bb473e00f..071904ffe 100644 --- a/css/app.css +++ b/css/app.css @@ -11,7 +11,10 @@ html, body { } body { - font:normal 12px/1.6667 'Helvetica Neue', Arial, sans-serif; + font: normal 12px/1.6667 -apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", + "Fira Sans", "Droid Sans", "Helvetica Neue", "Arial", + sans-serif; margin:0; padding:0; min-width: 768px; @@ -148,7 +151,10 @@ a:hover { textarea { resize: vertical; - font:normal 12px/20px 'Helvetica Neue', Arial, sans-serif; + font:normal 12px/20px -apple-system, BlinkMacSystemFont, + "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", + "Fira Sans", "Droid Sans", "Helvetica Neue", "Arial", + sans-serif; } textarea, diff --git a/data/address-formats.json b/data/address-formats.json index 973535963..18057351b 100644 --- a/data/address-formats.json +++ b/data/address-formats.json @@ -38,6 +38,14 @@ { "countryCodes": ["tw"], "format": [["postcode", "city", "district"], ["place", "street"], ["housenumber", "floor"]] + }, + { + "countryCodes": ["jp"], + "format": [["postcode", "province", "county"], ["city", "suburb", "quarter"], ["neighbourhood", "block_number", "housenumber"]] + }, + { + "countryCodes": ["tr"], + "format": [["neighbourhood"], ["street", "housenumber"], ["postcode", "district", "city"]] } ] } diff --git a/data/core.yaml b/data/core.yaml index 5e0344b81..916dfc6de 100644 --- a/data/core.yaml +++ b/data/core.yaml @@ -78,24 +78,24 @@ en: delete: title: Delete description: - single: Delete this object permanently. - multiple: Delete these objects permanently. + single: Delete this feature permanently. + multiple: Delete these features permanently. annotation: point: Deleted a point. vertex: Deleted a node from a way. line: Deleted a line. area: Deleted an area. relation: Deleted a relation. - multiple: "Deleted {n} objects." + multiple: "Deleted {n} features." incomplete_relation: - single: This object can't be deleted because it hasn't been fully downloaded. - multiple: These objects can't be deleted because they haven't been fully downloaded. + single: This feature can't be deleted because it hasn't been fully downloaded. + multiple: These features can't be deleted because they haven't been fully downloaded. part_of_relation: - single: This object can't be deleted because it is part of a larger relation. You must remove it from the relation first. - multiple: These objects can't be deleted because they are part of larger relations. You must remove them from the relations first. + single: This feature can't be deleted because it is part of a larger relation. You must remove it from the relation first. + multiple: These features can't be deleted because they are part of larger relations. You must remove them from the relations first. connected_to_hidden: - single: This object can't be deleted because it is connected to a hidden feature. - multiple: These objects can't be deleted because some are connected to hidden features. + single: This feature can't be deleted because it is connected to a hidden feature. + multiple: These features can't be deleted because some are connected to hidden features. add_member: annotation: Added a member to a relation. delete_member: @@ -127,71 +127,71 @@ en: move: title: Move description: - single: Move this object to a different location. - multiple: Move these objects to a different location. + single: Move this feature to a different location. + multiple: Move these features to a different location. key: M annotation: point: Moved a point. vertex: Moved a node in a way. line: Moved a line. area: Moved an area. - multiple: Moved multiple objects. + multiple: Moved multiple features. incomplete_relation: - single: This object can't be moved because it hasn't been fully downloaded. - multiple: These objects can't be moved because they haven't been fully downloaded. + single: This feature can't be moved because it hasn't been fully downloaded. + multiple: These features can't be moved because they haven't been fully downloaded. too_large: - single: This object can't be moved because not enough of it is currently visible. - multiple: These objects can't be moved because not enough of them are currently visible. + single: This feature can't be moved because not enough of it is currently visible. + multiple: These features can't be moved because not enough of them are currently visible. connected_to_hidden: - single: This object can't be moved because it is connected to a hidden feature. - multiple: These objects can't be moved because some are connected to hidden features. + single: This feature can't be moved because it is connected to a hidden feature. + multiple: These features can't be moved because some are connected to hidden features. reflect: title: reflect description: long: - single: Reflect this object across its long axis. - multiple: Reflect these objects across their long axis. + single: Reflect this feature across its long axis. + multiple: Reflect these features across their long axis. short: - single: Reflect this object across its short axis. - multiple: Reflect these objects across their short axis. + single: Reflect this feature across its short axis. + multiple: Reflect these features across their short axis. key: long: T short: Y annotation: long: - single: Reflected an object across its long axis. - multiple: Reflected multiple objects across their long axis. + single: Reflected an feature across its long axis. + multiple: Reflected multiple features across their long axis. short: - single: Reflected an object across its short axis. - multiple: Reflected multiple objects across their short axis. + single: Reflected an feature across its short axis. + multiple: Reflected multiple features across their short axis. incomplete_relation: - single: This object can't be reflected because it hasn't been fully downloaded. - multiple: These objects can't be reflected because they haven't been fully downloaded. + single: This feature can't be reflected because it hasn't been fully downloaded. + multiple: These features can't be reflected because they haven't been fully downloaded. too_large: - single: This object can't be reflected because not enough of it is currently visible. - multiple: These objects can't be reflected because not enough of them are currently visible. + single: This feature can't be reflected because not enough of it is currently visible. + multiple: These features can't be reflected because not enough of them are currently visible. connected_to_hidden: - single: This object can't be reflected because it is connected to a hidden feature. - multiple: These objects can't be reflected because some are connected to hidden features. + single: This feature can't be reflected because it is connected to a hidden feature. + multiple: These features can't be reflected because some are connected to hidden features. rotate: title: Rotate description: - single: Rotate this object around its center point. - multiple: Rotate these objects around their center point. + single: Rotate this feature around its center point. + multiple: Rotate these features around their center point. key: R annotation: line: Rotated a line. area: Rotated an area. - multiple: Rotated multiple objects. + multiple: Rotated multiple features. incomplete_relation: - single: This object can't be rotated because it hasn't been fully downloaded. - multiple: These objects can't be rotated because they haven't been fully downloaded. + single: This feature can't be rotated because it hasn't been fully downloaded. + multiple: These features can't be rotated because they haven't been fully downloaded. too_large: - single: This object can't be rotated because not enough of it is currently visible. - multiple: These objects can't be rotated because not enough of them are currently visible. + single: This feature can't be rotated because not enough of it is currently visible. + multiple: These features can't be rotated because not enough of them are currently visible. connected_to_hidden: - single: This object can't be rotated because it is connected to a hidden feature. - multiple: These objects can't be rotated because some are connected to hidden features. + single: This feature can't be rotated because it is connected to a hidden feature. + multiple: These features can't be rotated because some are connected to hidden features. reverse: title: Reverse description: Make this line go in the opposite direction. @@ -310,7 +310,7 @@ en: back_tooltip: Change feature remove: Remove search: Search - multiselect: Selected items + multiselect: Selected features unknown: Unknown incomplete: feature_list: Search features @@ -423,12 +423,12 @@ en: done: "All conflicts resolved!" help: | Another user changed some of the same map features you changed. - Click on each item below for more details about the conflict, and choose whether to keep + Click on each feature below for more details about the conflict, and choose whether to keep your changes or the other user's changes. merge_remote_changes: conflict: - deleted: 'This object has been deleted by {user}.' - location: 'This object was moved by both you and {user}.' + deleted: 'This feature has been deleted by {user}.' + location: 'This feature was moved by both you and {user}.' nodelist: 'Nodes were changed by both you and {user}.' memberlist: 'Relation members were changed by both you and {user}.' tags: 'You changed the {tag} tag to "{local}" and {user} changed it to "{remote}".' @@ -462,7 +462,7 @@ en: untagged_point: Untagged point untagged_line: Untagged line untagged_area: Untagged area - many_deletions: "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org." + many_deletions: "You're deleting {n} features. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org." tag_suggests_area: "The tag {tag} suggests line should be area, but it is not an area" untagged_point_tooltip: "Select a feature type that describes what this point is." untagged_line_tooltip: "Select a feature type that describes what this line is." diff --git a/data/presets.yaml b/data/presets.yaml index c2ddb18cf..cb0f7463d 100644 --- a/data/presets.yaml +++ b/data/presets.yaml @@ -78,20 +78,24 @@ en: # access=* label: Access address: - # 'addr:city=*, addr:conscriptionnumber=*, addr:country=*, addr:district=*, addr:floor=*, addr:hamlet=*, addr:housename=*, addr:housenumber=*, addr:place=*, addr:postcode=*, addr:province=*, addr:state=*, addr:street=*, addr:subdistrict=*, addr:suburb=*' + # 'addr:block_number=*, addr:city=*, addr:conscriptionnumber=*, addr:county=*, addr:country=*, addr:district=*, addr:floor=*, addr:hamlet=*, addr:housename=*, addr:housenumber=*, addr:neighbourhood=*, addr:place=*, addr:postcode=*, addr:province=*, addr:quarter=*, addr:state=*, addr:street=*, addr:subdistrict=*, addr:suburb=*' label: Address placeholders: + block_number: Block number city: City conscriptionnumber: '123' country: Country + county: County district: District floor: Floor hamlet: Hamlet housename: Housename housenumber: '123' + neighbourhood: Neighbourhood place: Place postcode: Postcode province: Province + quarter: Quarter state: State street: Street subdistrict: Subdistrict @@ -284,6 +288,9 @@ en: WNW: West-northwest # direction=WSW WSW: West-southwest + castle_type: + # castle_type=* + label: Type clock_direction: # direction=* label: Direction @@ -431,6 +438,18 @@ en: fence_type: # fence_type=* label: Type + fire_hydrant/position: + # 'fire_hydrant:position=*' + label: Position + options: + # 'fire_hydrant:position=green' + green: Green + # 'fire_hydrant:position=lane' + lane: Lane + # 'fire_hydrant:position=parking_lot' + parking_lot: Parking Lot + # 'fire_hydrant:position=sidewalk' + sidewalk: Sidewalk fire_hydrant/type: # 'fire_hydrant:type=*' label: Type @@ -1059,11 +1078,12 @@ en: very_horrible: 'Specialized off-road: tractor, ATV' # smoothness field placeholder placeholder: 'Thin Rollers, Wheels, Off-Road...' + social_facility: + # social_facility=* + label: Type social_facility_for: # 'social_facility:for=*' - label: People served - # social_facility_for field placeholder - placeholder: 'Homeless, Disabled, Child, etc' + label: People Served source: # source=* label: Source @@ -1161,6 +1181,9 @@ en: flush: Flush # 'toilets:disposal=pitlatrine' pitlatrine: Pit/Latrine + toll: + # toll=* + label: Toll tourism: # tourism=* label: Type @@ -1481,10 +1504,8 @@ en: name: Courthouse terms: '' amenity/coworking_space: - # amenity=coworking_space + # office=coworking name: Coworking Space - # 'terms: coworking,office' - terms: '' amenity/crematorium: # amenity=crematorium name: Crematorium @@ -1862,6 +1883,10 @@ en: # barrier=bollard name: Bollard terms: '' + barrier/border_control: + # barrier=border_control + name: Border Control + terms: '' barrier/cattle_grid: # barrier=cattle_grid name: Cattle Grid @@ -2298,6 +2323,7 @@ en: emergency/fire_hydrant: # emergency=fire_hydrant name: Fire Hydrant + # 'terms: fire plug' terms: '' emergency/no: # emergency=no @@ -2435,6 +2461,7 @@ en: highway/motorway: # highway=motorway name: Motorway + # 'terms: autobahn,expressway,freeway,highway,interstate,parkway,thruway,turnpike' terms: '' highway/motorway_junction: # highway=motorway_junction @@ -2841,6 +2868,11 @@ en: name: Golf Course # 'terms: links' terms: '' + leisure/horse_riding: + # leisure=horse_riding + name: Horseback Riding Facility + # 'terms: equestrian,stable' + terms: '' leisure/ice_rink: # leisure=ice_rink name: Ice Rink @@ -3231,6 +3263,11 @@ en: # office=company name: Company Office terms: '' + office/coworking: + # office=coworking + name: Coworking Space + # 'terms: coworking,office' + terms: '' office/educational_institution: # office=educational_institution name: Educational Institution @@ -3338,6 +3375,10 @@ en: name: Neighborhood # 'terms: neighbourhood' terms: '' + place/square: + # place=square + name: Square + terms: '' place/suburb: # place=suburb name: Borough diff --git a/data/presets/fields.json b/data/presets/fields.json index ffe867115..3a35f2386 100644 --- a/data/presets/fields.json +++ b/data/presets/fields.json @@ -79,17 +79,21 @@ "address": { "type": "address", "keys": [ + "addr:block_number", "addr:city", "addr:conscriptionnumber", + "addr:county", "addr:country", "addr:district", "addr:floor", "addr:hamlet", "addr:housename", "addr:housenumber", + "addr:neighbourhood", "addr:place", "addr:postcode", "addr:province", + "addr:quarter", "addr:state", "addr:street", "addr:subdistrict", @@ -103,17 +107,21 @@ "label": "Address", "strings": { "placeholders": { + "block_number": "Block number", "city": "City", "conscriptionnumber": "123", + "county": "County", "country": "Country", "district": "District", "floor": "Floor", "hamlet": "Hamlet", "housename": "Housename", "housenumber": "123", + "neighbourhood": "Neighbourhood", "place": "Place", "postcode": "Postcode", "province": "Province", + "quarter": "Quarter", "state": "State", "street": "Street", "subdistrict": "Subdistrict", @@ -364,6 +372,11 @@ } } }, + "castle_type": { + "key": "castle_type", + "type": "combo", + "label": "Type" + }, "clock_direction": { "key": "direction", "type": "combo", @@ -599,6 +612,19 @@ "type": "combo", "label": "Type" }, + "fire_hydrant/position": { + "key": "fire_hydrant:position", + "type": "combo", + "label": "Position", + "strings": { + "options": { + "lane": "Lane", + "parking_lot": "Parking Lot", + "sidewalk": "Sidewalk", + "green": "Green" + } + } + }, "fire_hydrant/type": { "key": "fire_hydrant:type", "type": "combo", @@ -1404,25 +1430,13 @@ }, "social_facility_for": { "key": "social_facility:for", - "type": "radio", - "label": "People served", - "placeholder": "Homeless, Disabled, Child, etc", - "options": [ - "abused", - "child", - "disabled", - "diseased", - "drug_addicted", - "homeless", - "juvenile", - "mental_health", - "migrant", - "orphan", - "senior", - "underprivileged", - "unemployed", - "victim" - ] + "type": "combo", + "label": "People Served" + }, + "social_facility": { + "key": "social_facility", + "type": "combo", + "label": "Type" }, "source": { "key": "source", @@ -1577,6 +1591,11 @@ } } }, + "toll": { + "key": "toll", + "type": "check", + "label": "Toll" + }, "tourism": { "key": "tourism", "type": "typeCombo", diff --git a/data/presets/fields/address.json b/data/presets/fields/address.json index 9d223b744..ff1e5f505 100644 --- a/data/presets/fields/address.json +++ b/data/presets/fields/address.json @@ -1,17 +1,21 @@ { "type": "address", "keys": [ + "addr:block_number", "addr:city", "addr:conscriptionnumber", + "addr:county", "addr:country", "addr:district", "addr:floor", "addr:hamlet", "addr:housename", "addr:housenumber", + "addr:neighbourhood", "addr:place", "addr:postcode", "addr:province", + "addr:quarter", "addr:state", "addr:street", "addr:subdistrict", @@ -23,17 +27,21 @@ "label": "Address", "strings": { "placeholders": { + "block_number": "Block number", "city": "City", "conscriptionnumber": "123", + "county": "County", "country": "Country", "district": "District", "floor": "Floor", "hamlet": "Hamlet", "housename": "Housename", "housenumber": "123", + "neighbourhood": "Neighbourhood", "place": "Place", "postcode": "Postcode", "province": "Province", + "quarter": "Quarter", "state": "State", "street": "Street", "subdistrict": "Subdistrict", diff --git a/data/presets/fields/castle_type.json b/data/presets/fields/castle_type.json new file mode 100644 index 000000000..ea36ca17d --- /dev/null +++ b/data/presets/fields/castle_type.json @@ -0,0 +1,5 @@ +{ + "key": "castle_type", + "type": "combo", + "label": "Type" +} diff --git a/data/presets/fields/fire_hydrant/position.json b/data/presets/fields/fire_hydrant/position.json new file mode 100644 index 000000000..0d2e245ec --- /dev/null +++ b/data/presets/fields/fire_hydrant/position.json @@ -0,0 +1,13 @@ +{ + "key": "fire_hydrant:position", + "type": "combo", + "label": "Position", + "strings": { + "options": { + "lane": "Lane", + "parking_lot": "Parking Lot", + "sidewalk": "Sidewalk", + "green": "Green" + } + } +} diff --git a/data/presets/fields/social_facility.json b/data/presets/fields/social_facility.json new file mode 100644 index 000000000..901e38fa8 --- /dev/null +++ b/data/presets/fields/social_facility.json @@ -0,0 +1,5 @@ +{ + "key": "social_facility", + "type": "combo", + "label": "Type" +} diff --git a/data/presets/fields/social_facility_for.json b/data/presets/fields/social_facility_for.json index cf4a6212f..960201be5 100644 --- a/data/presets/fields/social_facility_for.json +++ b/data/presets/fields/social_facility_for.json @@ -1,23 +1,5 @@ { "key": "social_facility:for", - "type": "radio", - "label": "People served", - "placeholder": "Homeless, Disabled, Child, etc", - "options": [ - "abused", - "child", - "disabled", - "diseased", - "drug_addicted", - "homeless", - "juvenile", - "mental_health", - "migrant", - "orphan", - "senior", - "underprivileged", - "unemployed", - "victim" - ] - -} \ No newline at end of file + "type": "combo", + "label": "People Served" +} diff --git a/data/presets/fields/toll.json b/data/presets/fields/toll.json new file mode 100644 index 000000000..4c5cb4999 --- /dev/null +++ b/data/presets/fields/toll.json @@ -0,0 +1,5 @@ +{ + "key": "toll", + "type": "check", + "label": "Toll" +} diff --git a/data/presets/presets.json b/data/presets/presets.json index bc329aae5..8b89f52ad 100644 --- a/data/presets/presets.json +++ b/data/presets/presets.json @@ -445,6 +445,26 @@ }, "name": "Airport terminal" }, + "amenity/coworking_space": { + "icon": "commercial", + "fields": [ + "address", + "building_area", + "opening_hours", + "internet_access", + "internet_access/fee", + "internet_access/ssid" + ], + "geometry": [ + "point", + "area" + ], + "tags": { + "office": "coworking" + }, + "name": "Coworking Space", + "searchable": false + }, "amenity/register_office": { "icon": "town-hall", "fields": [ @@ -1015,29 +1035,6 @@ }, "name": "Courthouse" }, - "amenity/coworking_space": { - "icon": "commercial", - "fields": [ - "address", - "building_area", - "opening_hours", - "internet_access", - "internet_access/fee", - "internet_access/ssid" - ], - "geometry": [ - "point", - "area" - ], - "terms": [ - "coworking", - "office" - ], - "tags": { - "amenity": "coworking_space" - }, - "name": "Coworking Space" - }, "amenity/crematorium": { "icon": "cemetery", "fields": [ @@ -2109,9 +2106,10 @@ "operator", "address", "building_area", + "social_facility", + "social_facility_for", "opening_hours", - "wheelchair", - "social_facility_for" + "wheelchair" ], "geometry": [ "point", @@ -2128,8 +2126,9 @@ "operator", "address", "building_area", - "opening_hours", - "social_facility_for" + "social_facility", + "social_facility_for", + "opening_hours" ], "geometry": [ "point", @@ -2147,9 +2146,10 @@ "operator", "address", "building_area", + "social_facility", + "social_facility_for", "opening_hours", - "wheelchair", - "social_facility_for" + "wheelchair" ], "geometry": [ "point", @@ -2172,9 +2172,10 @@ "operator", "address", "building_area", + "social_facility", + "social_facility_for", "opening_hours", "wheelchair", - "social_facility_for", "internet_access", "internet_access/fee", "internet_access/ssid" @@ -2729,6 +2730,21 @@ }, "name": "Bollard" }, + "barrier/border_control": { + "icon": "roadblock", + "fields": [ + "access", + "building_area" + ], + "geometry": [ + "vertex", + "area" + ], + "tags": { + "barrier": "border_control" + }, + "name": "Border Control" + }, "barrier/cattle_grid": { "icon": "prison", "geometry": [ @@ -2876,10 +2892,12 @@ "barrier/toll_booth": { "icon": "roadblock", "fields": [ - "access" + "access", + "building_area" ], "geometry": [ - "vertex" + "vertex", + "area" ], "tags": { "barrier": "toll_booth" @@ -4511,12 +4529,16 @@ "emergency/fire_hydrant": { "icon": "fire-station", "fields": [ - "fire_hydrant/type" + "fire_hydrant/type", + "fire_hydrant/position" ], "geometry": [ "point", "vertex" ], + "terms": [ + "fire plug" + ], "tags": { "emergency": "fire_hydrant" }, @@ -5013,6 +5035,7 @@ "lanes", "surface", "maxheight", + "toll", "ref" ], "geometry": [ @@ -5021,7 +5044,16 @@ "tags": { "highway": "motorway" }, - "terms": [], + "terms": [ + "autobahn", + "expressway", + "freeway", + "highway", + "interstate", + "parkway", + "thruway", + "turnpike" + ], "name": "Motorway" }, "highway/path": { @@ -5492,12 +5524,12 @@ "highway/track": { "icon": "highway-track", "fields": [ + "tracktype", "surface", "width", "structure", "access", "incline", - "tracktype", "smoothness", "mtb/scale", "mtb/scale/uphill", @@ -5605,6 +5637,7 @@ "lanes", "surface", "maxheight", + "toll", "ref" ], "geometry": [ @@ -5699,6 +5732,10 @@ "name": "Boundary Stone" }, "historic/castle": { + "fields": [ + "castle_type", + "building_area" + ], "geometry": [ "point", "area" @@ -6697,6 +6734,26 @@ }, "name": "Golf Course" }, + "leisure/horse_riding": { + "fields": [ + "access_simple", + "operator", + "address", + "building" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "equestrian", + "stable" + ], + "tags": { + "leisure": "horse_riding" + }, + "name": "Horseback Riding Facility" + }, "leisure/ice_rink": { "icon": "pitch", "fields": [ @@ -8156,6 +8213,29 @@ "terms": [], "name": "Company Office" }, + "office/coworking": { + "icon": "commercial", + "fields": [ + "address", + "building_area", + "opening_hours", + "internet_access", + "internet_access/fee", + "internet_access/ssid" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "coworking", + "office" + ], + "tags": { + "office": "coworking" + }, + "name": "Coworking Space" + }, "office/educational_institution": { "icon": "commercial", "fields": [ @@ -8579,6 +8659,16 @@ ], "name": "Neighborhood" }, + "place/square": { + "geometry": [ + "point", + "area" + ], + "tags": { + "place": "square" + }, + "name": "Square" + }, "place/suburb": { "icon": "triangle-stroked", "fields": [ diff --git a/data/presets/presets/amenity/_coworking_space.json b/data/presets/presets/amenity/_coworking_space.json new file mode 100644 index 000000000..876080d71 --- /dev/null +++ b/data/presets/presets/amenity/_coworking_space.json @@ -0,0 +1,20 @@ +{ + "icon": "commercial", + "fields": [ + "address", + "building_area", + "opening_hours", + "internet_access", + "internet_access/fee", + "internet_access/ssid" + ], + "geometry": [ + "point", + "area" + ], + "tags": { + "office": "coworking" + }, + "name": "Coworking Space", + "searchable": false +} diff --git a/data/presets/presets/amenity/crematorium.json b/data/presets/presets/amenity/crematorium.json index acfca6ada..cde1bbe3f 100644 --- a/data/presets/presets/amenity/crematorium.json +++ b/data/presets/presets/amenity/crematorium.json @@ -5,10 +5,9 @@ "phone", "opening_hours", "wheelchair" - ], "geometry": [ - "area", + "area", "point" ], "tags": { @@ -16,4 +15,4 @@ }, "terms": ["cemetery","funeral"], "name": "Crematorium" -} \ No newline at end of file +} diff --git a/data/presets/presets/amenity/social_facility.json b/data/presets/presets/amenity/social_facility.json index 926a814b8..58dfa0402 100644 --- a/data/presets/presets/amenity/social_facility.json +++ b/data/presets/presets/amenity/social_facility.json @@ -3,16 +3,16 @@ "operator", "address", "building_area", + "social_facility", + "social_facility_for", "opening_hours", - "wheelchair", - "social_facility_for" + "wheelchair" ], "geometry": [ "point", "area" ], - "terms": [ - ], + "terms": [], "tags": { "amenity": "social_facility" }, diff --git a/data/presets/presets/amenity/social_facility/food_bank.json b/data/presets/presets/amenity/social_facility/food_bank.json index 46053afac..680a7619d 100644 --- a/data/presets/presets/amenity/social_facility/food_bank.json +++ b/data/presets/presets/amenity/social_facility/food_bank.json @@ -3,8 +3,9 @@ "operator", "address", "building_area", - "opening_hours", - "social_facility_for" + "social_facility", + "social_facility_for", + "opening_hours" ], "geometry": [ "point", diff --git a/data/presets/presets/amenity/social_facility/group_home.json b/data/presets/presets/amenity/social_facility/group_home.json index ccf07da2a..620540fac 100644 --- a/data/presets/presets/amenity/social_facility/group_home.json +++ b/data/presets/presets/amenity/social_facility/group_home.json @@ -3,9 +3,10 @@ "operator", "address", "building_area", + "social_facility", + "social_facility_for", "opening_hours", - "wheelchair", - "social_facility_for" + "wheelchair" ], "geometry": [ "point", diff --git a/data/presets/presets/amenity/social_facility/homeless_shelter.json b/data/presets/presets/amenity/social_facility/homeless_shelter.json index 717ddece7..96b6128af 100644 --- a/data/presets/presets/amenity/social_facility/homeless_shelter.json +++ b/data/presets/presets/amenity/social_facility/homeless_shelter.json @@ -3,9 +3,10 @@ "operator", "address", "building_area", + "social_facility", + "social_facility_for", "opening_hours", "wheelchair", - "social_facility_for", "internet_access", "internet_access/fee", "internet_access/ssid" diff --git a/data/presets/presets/barrier/border_control.json b/data/presets/presets/barrier/border_control.json new file mode 100644 index 000000000..ecb669ccc --- /dev/null +++ b/data/presets/presets/barrier/border_control.json @@ -0,0 +1,15 @@ +{ + "icon": "roadblock", + "fields": [ + "access", + "building_area" + ], + "geometry": [ + "vertex", + "area" + ], + "tags": { + "barrier": "border_control" + }, + "name": "Border Control" +} diff --git a/data/presets/presets/barrier/toll_booth.json b/data/presets/presets/barrier/toll_booth.json index 83059fcb9..618fb8a56 100644 --- a/data/presets/presets/barrier/toll_booth.json +++ b/data/presets/presets/barrier/toll_booth.json @@ -1,10 +1,12 @@ { "icon": "roadblock", "fields": [ - "access" + "access", + "building_area" ], "geometry": [ - "vertex" + "vertex", + "area" ], "tags": { "barrier": "toll_booth" diff --git a/data/presets/presets/emergency/fire_hydrant.json b/data/presets/presets/emergency/fire_hydrant.json index 11fb9155f..e0a161040 100644 --- a/data/presets/presets/emergency/fire_hydrant.json +++ b/data/presets/presets/emergency/fire_hydrant.json @@ -1,12 +1,16 @@ { "icon": "fire-station", "fields": [ - "fire_hydrant/type" + "fire_hydrant/type", + "fire_hydrant/position" ], "geometry": [ "point", "vertex" ], + "terms": [ + "fire plug" + ], "tags": { "emergency": "fire_hydrant" }, diff --git a/data/presets/presets/highway/motorway.json b/data/presets/presets/highway/motorway.json index 28adc22ad..6a86898a2 100644 --- a/data/presets/presets/highway/motorway.json +++ b/data/presets/presets/highway/motorway.json @@ -8,6 +8,7 @@ "lanes", "surface", "maxheight", + "toll", "ref" ], "geometry": [ @@ -16,6 +17,15 @@ "tags": { "highway": "motorway" }, - "terms": [], + "terms": [ + "autobahn", + "expressway", + "freeway", + "highway", + "interstate", + "parkway", + "thruway", + "turnpike" + ], "name": "Motorway" } diff --git a/data/presets/presets/highway/track.json b/data/presets/presets/highway/track.json index 58b6d0807..50a59cb18 100644 --- a/data/presets/presets/highway/track.json +++ b/data/presets/presets/highway/track.json @@ -1,12 +1,12 @@ { "icon": "highway-track", "fields": [ + "tracktype", "surface", "width", "structure", "access", "incline", - "tracktype", "smoothness", "mtb/scale", "mtb/scale/uphill", diff --git a/data/presets/presets/highway/trunk.json b/data/presets/presets/highway/trunk.json index a820c1e9d..a91724862 100644 --- a/data/presets/presets/highway/trunk.json +++ b/data/presets/presets/highway/trunk.json @@ -8,6 +8,7 @@ "lanes", "surface", "maxheight", + "toll", "ref" ], "geometry": [ diff --git a/data/presets/presets/historic/castle.json b/data/presets/presets/historic/castle.json index fed1adb06..0d9960576 100644 --- a/data/presets/presets/historic/castle.json +++ b/data/presets/presets/historic/castle.json @@ -1,4 +1,8 @@ { + "fields": [ + "castle_type", + "building_area" + ], "geometry": [ "point", "area" diff --git a/data/presets/presets/leisure/horse_riding.json b/data/presets/presets/leisure/horse_riding.json new file mode 100644 index 000000000..d8759f4ac --- /dev/null +++ b/data/presets/presets/leisure/horse_riding.json @@ -0,0 +1,20 @@ +{ + "fields": [ + "access_simple", + "operator", + "address", + "building" + ], + "geometry": [ + "point", + "area" + ], + "terms": [ + "equestrian", + "stable" + ], + "tags": { + "leisure": "horse_riding" + }, + "name": "Horseback Riding Facility" +} diff --git a/data/presets/presets/amenity/coworking_space.json b/data/presets/presets/office/coworking.json similarity index 91% rename from data/presets/presets/amenity/coworking_space.json rename to data/presets/presets/office/coworking.json index d10e9135a..e02afb6c5 100644 --- a/data/presets/presets/amenity/coworking_space.json +++ b/data/presets/presets/office/coworking.json @@ -17,7 +17,7 @@ "office" ], "tags": { - "amenity": "coworking_space" + "office": "coworking" }, "name": "Coworking Space" } diff --git a/data/presets/presets/place/square.json b/data/presets/presets/place/square.json new file mode 100644 index 000000000..b08ef8ec9 --- /dev/null +++ b/data/presets/presets/place/square.json @@ -0,0 +1,10 @@ +{ + "geometry": [ + "point", + "area" + ], + "tags": { + "place": "square" + }, + "name": "Square" +} diff --git a/data/taginfo.json b/data/taginfo.json index e8f419cb0..14ed9dc67 100644 --- a/data/taginfo.json +++ b/data/taginfo.json @@ -115,6 +115,10 @@ "key": "aeroway", "value": "terminal" }, + { + "key": "office", + "value": "coworking" + }, { "key": "amenity", "value": "register_office" @@ -231,10 +235,6 @@ "key": "amenity", "value": "courthouse" }, - { - "key": "amenity", - "value": "coworking_space" - }, { "key": "amenity", "value": "crematorium" @@ -549,6 +549,10 @@ "key": "barrier", "value": "bollard" }, + { + "key": "barrier", + "value": "border_control" + }, { "key": "barrier", "value": "cattle_grid" @@ -1447,6 +1451,10 @@ "key": "leisure", "value": "golf_course" }, + { + "key": "leisure", + "value": "horse_riding" + }, { "key": "leisure", "value": "ice_rink" @@ -1796,6 +1804,10 @@ "key": "office", "value": "company" }, + { + "key": "office", + "value": "coworking" + }, { "key": "office", "value": "educational_institution" @@ -1891,6 +1903,10 @@ "key": "place", "value": "neighbourhood" }, + { + "key": "place", + "value": "square" + }, { "key": "place", "value": "suburb" diff --git a/dist/locales/en.json b/dist/locales/en.json index ef15e5fa9..529ae0498 100644 --- a/dist/locales/en.json +++ b/dist/locales/en.json @@ -102,8 +102,8 @@ "delete": { "title": "Delete", "description": { - "single": "Delete this object permanently.", - "multiple": "Delete these objects permanently." + "single": "Delete this feature permanently.", + "multiple": "Delete these features permanently." }, "annotation": { "point": "Deleted a point.", @@ -111,19 +111,19 @@ "line": "Deleted a line.", "area": "Deleted an area.", "relation": "Deleted a relation.", - "multiple": "Deleted {n} objects." + "multiple": "Deleted {n} features." }, "incomplete_relation": { - "single": "This object can't be deleted because it hasn't been fully downloaded.", - "multiple": "These objects can't be deleted because they haven't been fully downloaded." + "single": "This feature can't be deleted because it hasn't been fully downloaded.", + "multiple": "These features can't be deleted because they haven't been fully downloaded." }, "part_of_relation": { - "single": "This object can't be deleted because it is part of a larger relation. You must remove it from the relation first.", - "multiple": "These objects can't be deleted because they are part of larger relations. You must remove them from the relations first." + "single": "This feature can't be deleted because it is part of a larger relation. You must remove it from the relation first.", + "multiple": "These features can't be deleted because they are part of larger relations. You must remove them from the relations first." }, "connected_to_hidden": { - "single": "This object can't be deleted because it is connected to a hidden feature.", - "multiple": "These objects can't be deleted because some are connected to hidden features." + "single": "This feature can't be deleted because it is connected to a hidden feature.", + "multiple": "These features can't be deleted because some are connected to hidden features." } }, "add_member": { @@ -163,8 +163,8 @@ "move": { "title": "Move", "description": { - "single": "Move this object to a different location.", - "multiple": "Move these objects to a different location." + "single": "Move this feature to a different location.", + "multiple": "Move these features to a different location." }, "key": "M", "annotation": { @@ -172,31 +172,31 @@ "vertex": "Moved a node in a way.", "line": "Moved a line.", "area": "Moved an area.", - "multiple": "Moved multiple objects." + "multiple": "Moved multiple features." }, "incomplete_relation": { - "single": "This object can't be moved because it hasn't been fully downloaded.", - "multiple": "These objects can't be moved because they haven't been fully downloaded." + "single": "This feature can't be moved because it hasn't been fully downloaded.", + "multiple": "These features can't be moved because they haven't been fully downloaded." }, "too_large": { - "single": "This object can't be moved because not enough of it is currently visible.", - "multiple": "These objects can't be moved because not enough of them are currently visible." + "single": "This feature can't be moved because not enough of it is currently visible.", + "multiple": "These features can't be moved because not enough of them are currently visible." }, "connected_to_hidden": { - "single": "This object can't be moved because it is connected to a hidden feature.", - "multiple": "These objects can't be moved because some are connected to hidden features." + "single": "This feature can't be moved because it is connected to a hidden feature.", + "multiple": "These features can't be moved because some are connected to hidden features." } }, "reflect": { "title": "reflect", "description": { "long": { - "single": "Reflect this object across its long axis.", - "multiple": "Reflect these objects across their long axis." + "single": "Reflect this feature across its long axis.", + "multiple": "Reflect these features across their long axis." }, "short": { - "single": "Reflect this object across its short axis.", - "multiple": "Reflect these objects across their short axis." + "single": "Reflect this feature across its short axis.", + "multiple": "Reflect these features across their short axis." } }, "key": { @@ -205,50 +205,50 @@ }, "annotation": { "long": { - "single": "Reflected an object across its long axis.", - "multiple": "Reflected multiple objects across their long axis." + "single": "Reflected an feature across its long axis.", + "multiple": "Reflected multiple features across their long axis." }, "short": { - "single": "Reflected an object across its short axis.", - "multiple": "Reflected multiple objects across their short axis." + "single": "Reflected an feature across its short axis.", + "multiple": "Reflected multiple features across their short axis." } }, "incomplete_relation": { - "single": "This object can't be reflected because it hasn't been fully downloaded.", - "multiple": "These objects can't be reflected because they haven't been fully downloaded." + "single": "This feature can't be reflected because it hasn't been fully downloaded.", + "multiple": "These features can't be reflected because they haven't been fully downloaded." }, "too_large": { - "single": "This object can't be reflected because not enough of it is currently visible.", - "multiple": "These objects can't be reflected because not enough of them are currently visible." + "single": "This feature can't be reflected because not enough of it is currently visible.", + "multiple": "These features can't be reflected because not enough of them are currently visible." }, "connected_to_hidden": { - "single": "This object can't be reflected because it is connected to a hidden feature.", - "multiple": "These objects can't be reflected because some are connected to hidden features." + "single": "This feature can't be reflected because it is connected to a hidden feature.", + "multiple": "These features can't be reflected because some are connected to hidden features." } }, "rotate": { "title": "Rotate", "description": { - "single": "Rotate this object around its center point.", - "multiple": "Rotate these objects around their center point." + "single": "Rotate this feature around its center point.", + "multiple": "Rotate these features around their center point." }, "key": "R", "annotation": { "line": "Rotated a line.", "area": "Rotated an area.", - "multiple": "Rotated multiple objects." + "multiple": "Rotated multiple features." }, "incomplete_relation": { - "single": "This object can't be rotated because it hasn't been fully downloaded.", - "multiple": "These objects can't be rotated because they haven't been fully downloaded." + "single": "This feature can't be rotated because it hasn't been fully downloaded.", + "multiple": "These features can't be rotated because they haven't been fully downloaded." }, "too_large": { - "single": "This object can't be rotated because not enough of it is currently visible.", - "multiple": "These objects can't be rotated because not enough of them are currently visible." + "single": "This feature can't be rotated because not enough of it is currently visible.", + "multiple": "These features can't be rotated because not enough of them are currently visible." }, "connected_to_hidden": { - "single": "This object can't be rotated because it is connected to a hidden feature.", - "multiple": "These objects can't be rotated because some are connected to hidden features." + "single": "This feature can't be rotated because it is connected to a hidden feature.", + "multiple": "These features can't be rotated because some are connected to hidden features." } }, "reverse": { @@ -388,7 +388,7 @@ "back_tooltip": "Change feature", "remove": "Remove", "search": "Search", - "multiselect": "Selected items", + "multiselect": "Selected features", "unknown": "Unknown", "incomplete": "", "feature_list": "Search features", @@ -522,13 +522,13 @@ "delete": "Leave Deleted", "download_changes": "Or download your changes.", "done": "All conflicts resolved!", - "help": "Another user changed some of the same map features you changed.\nClick on each item below for more details about the conflict, and choose whether to keep\nyour changes or the other user's changes.\n" + "help": "Another user changed some of the same map features you changed.\nClick on each feature below for more details about the conflict, and choose whether to keep\nyour changes or the other user's changes.\n" } }, "merge_remote_changes": { "conflict": { - "deleted": "This object has been deleted by {user}.", - "location": "This object was moved by both you and {user}.", + "deleted": "This feature has been deleted by {user}.", + "location": "This feature was moved by both you and {user}.", "nodelist": "Nodes were changed by both you and {user}.", "memberlist": "Relation members were changed by both you and {user}.", "tags": "You changed the {tag} tag to \"{local}\" and {user} changed it to \"{remote}\"." @@ -569,7 +569,7 @@ "untagged_point": "Untagged point", "untagged_line": "Untagged line", "untagged_area": "Untagged area", - "many_deletions": "You're deleting {n} objects. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", + "many_deletions": "You're deleting {n} features. Are you sure you want to do this? This will delete them from the map that everyone else sees on openstreetmap.org.", "tag_suggests_area": "The tag {tag} suggests line should be area, but it is not an area", "untagged_point_tooltip": "Select a feature type that describes what this point is.", "untagged_line_tooltip": "Select a feature type that describes what this line is.", @@ -780,17 +780,21 @@ "address": { "label": "Address", "placeholders": { + "block_number": "Block number", "city": "City", "conscriptionnumber": "123", + "county": "County", "country": "Country", "district": "District", "floor": "Floor", "hamlet": "Hamlet", "housename": "Housename", "housenumber": "123", + "neighbourhood": "Neighbourhood", "place": "Place", "postcode": "Postcode", "province": "Province", + "quarter": "Quarter", "state": "State", "street": "Street", "subdistrict": "Subdistrict", @@ -954,6 +958,9 @@ "NNW": "North-northwest" } }, + "castle_type": { + "label": "Type" + }, "clock_direction": { "label": "Direction", "options": { @@ -1095,6 +1102,15 @@ "fence_type": { "label": "Type" }, + "fire_hydrant/position": { + "label": "Position", + "options": { + "lane": "Lane", + "parking_lot": "Parking Lot", + "sidewalk": "Sidewalk", + "green": "Green" + } + }, "fire_hydrant/type": { "label": "Type", "options": { @@ -1604,8 +1620,10 @@ } }, "social_facility_for": { - "label": "People served", - "placeholder": "Homeless, Disabled, Child, etc" + "label": "People Served" + }, + "social_facility": { + "label": "Type" }, "source": { "label": "Source" @@ -1690,6 +1708,9 @@ "bucket": "Bucket" } }, + "toll": { + "label": "Toll" + }, "tourism": { "label": "Type" }, @@ -1888,6 +1909,10 @@ "name": "Airport terminal", "terms": "airport,aerodrome" }, + "amenity/coworking_space": { + "name": "Coworking Space", + "terms": "" + }, "amenity/register_office": { "name": "Register Office", "terms": "" @@ -2004,10 +2029,6 @@ "name": "Courthouse", "terms": "" }, - "amenity/coworking_space": { - "name": "Coworking Space", - "terms": "coworking,office" - }, "amenity/crematorium": { "name": "Crematorium", "terms": "cemetery,funeral" @@ -2324,6 +2345,10 @@ "name": "Bollard", "terms": "" }, + "barrier/border_control": { + "name": "Border Control", + "terms": "" + }, "barrier/cattle_grid": { "name": "Cattle Grid", "terms": "" @@ -2754,7 +2779,7 @@ }, "emergency/fire_hydrant": { "name": "Fire Hydrant", - "terms": "" + "terms": "fire plug" }, "emergency/phone": { "name": "Emergency Phone", @@ -2874,7 +2899,7 @@ }, "highway/motorway": { "name": "Motorway", - "terms": "" + "terms": "autobahn,expressway,freeway,highway,interstate,parkway,thruway,turnpike" }, "highway/path": { "name": "Path", @@ -3228,6 +3253,10 @@ "name": "Golf Course", "terms": "links" }, + "leisure/horse_riding": { + "name": "Horseback Riding Facility", + "terms": "equestrian,stable" + }, "leisure/ice_rink": { "name": "Ice Rink", "terms": "hockey,skating,curling" @@ -3584,6 +3613,10 @@ "name": "Company Office", "terms": "" }, + "office/coworking": { + "name": "Coworking Space", + "terms": "coworking,office" + }, "office/educational_institution": { "name": "Educational Institution", "terms": "" @@ -3680,6 +3713,10 @@ "name": "Neighborhood", "terms": "neighbourhood" }, + "place/square": { + "name": "Square", + "terms": "" + }, "place/suburb": { "name": "Borough", "terms": "Boro,Quarter" diff --git a/modules/actions/circularize.js b/modules/actions/circularize.js index 283cd829e..6a14124fc 100644 --- a/modules/actions/circularize.js +++ b/modules/actions/circularize.js @@ -14,8 +14,16 @@ export function actionCircularize(wayId, projection, maxAngle) { maxAngle = (maxAngle || 20) * Math.PI / 180; - var action = function(graph) { - var way = graph.entity(wayId); + var action = function(graph, t) { + if (t === null || !isFinite(t)) t = 1; + t = Math.min(Math.max(+t, 0), 1); + + var way = graph.entity(wayId), + origNodes = {}; + + graph.childNodes(way).forEach(function(node) { + if (!origNodes[node.id]) origNodes[node.id] = node; + }); if (!way.isConvex(graph)) { graph = action.makeConvex(graph); @@ -56,21 +64,27 @@ export function actionCircularize(wayId, projection, maxAngle) { endNodeIndex = nodes.indexOf(endNode), numberNewPoints = -1, indexRange = endNodeIndex - startNodeIndex, - distance, totalAngle, eachAngle, startAngle, endAngle, - angle, loc, node, j, - inBetweenNodes = []; + nearNodes = {}, + inBetweenNodes = [], + startAngle, endAngle, totalAngle, eachAngle, + angle, loc, node, origNode, j; if (indexRange < 0) { indexRange += nodes.length; } // position this key node - distance = geoEuclideanDistance(centroid, keyPoints[i]); + var distance = geoEuclideanDistance(centroid, keyPoints[i]); if (distance === 0) { distance = 1e-4; } keyPoints[i] = [ centroid[0] + (keyPoints[i][0] - centroid[0]) / distance * radius, - centroid[1] + (keyPoints[i][1] - centroid[1]) / distance * radius]; - graph = graph.replace(keyNodes[i].move(projection.invert(keyPoints[i]))); + centroid[1] + (keyPoints[i][1] - centroid[1]) / distance * radius + ]; + loc = projection.invert(keyPoints[i]); + node = keyNodes[i]; + origNode = origNodes[node.id]; + node = node.move(geoInterp(origNode.loc, loc, t)); + graph = graph.replace(node); // figure out the between delta angle we want to match to startAngle = Math.atan2(keyPoints[i][1] - centroid[1], keyPoints[i][0] - centroid[0]); @@ -87,14 +101,20 @@ export function actionCircularize(wayId, projection, maxAngle) { eachAngle = totalAngle / (indexRange + numberNewPoints); } while (Math.abs(eachAngle) > maxAngle); - // move existing points + + // move existing nodes for (j = 1; j < indexRange; j++) { angle = startAngle + j * eachAngle; loc = projection.invert([ - centroid[0] + Math.cos(angle)*radius, - centroid[1] + Math.sin(angle)*radius]); + centroid[0] + Math.cos(angle) * radius, + centroid[1] + Math.sin(angle) * radius + ]); - node = nodes[(j + startNodeIndex) % nodes.length].move(loc); + node = nodes[(j + startNodeIndex) % nodes.length]; + origNode = origNodes[node.id]; + nearNodes[node.id] = angle; + + node = node.move(geoInterp(origNode.loc, loc, t)); graph = graph.replace(node); } @@ -103,9 +123,21 @@ export function actionCircularize(wayId, projection, maxAngle) { angle = startAngle + (indexRange + j) * eachAngle; loc = projection.invert([ centroid[0] + Math.cos(angle) * radius, - centroid[1] + Math.sin(angle) * radius]); + centroid[1] + Math.sin(angle) * radius + ]); - node = osmNode({loc: loc}); + // choose a nearnode to use as the original + var min = Infinity; + for (var nodeId in nearNodes) { + var nearAngle = nearNodes[nodeId], + dist = Math.abs(nearAngle - angle); + if (dist < min) { + dist = min; + origNode = origNodes[nodeId]; + } + } + + node = osmNode({ loc: geoInterp(origNode.loc, loc, t) }); graph = graph.replace(node); nodes.splice(endNodeIndex + j, 0, node); @@ -195,5 +227,8 @@ export function actionCircularize(wayId, projection, maxAngle) { }; + action.transitionable = true; + + return action; } diff --git a/modules/actions/merge.js b/modules/actions/merge.js index a2ff40ffc..2797253f7 100644 --- a/modules/actions/merge.js +++ b/modules/actions/merge.js @@ -17,15 +17,34 @@ export function actionMerge(ids) { points.forEach(function(point) { target = target.mergeTags(point.tags); + graph = graph.replace(target); graph.parentRelations(point).forEach(function(parent) { graph = graph.replace(parent.replaceMember(point, target)); }); - graph = graph.remove(point); - }); + var nodes = _.uniq(graph.childNodes(target)), + removeNode = point; - graph = graph.replace(target); + for (var i = 0; i < nodes.length; i++) { + var node = nodes[i]; + if (graph.parentWays(node).length > 1 || + graph.parentRelations(node).length || + node.hasInterestingTags()) { + continue; + } + + // Found an uninteresting child node on the target way. + // Move orig point into its place to preserve point's history. #3683 + graph = graph.replace(point.update({ tags: {}, loc: node.loc })); + target = target.replaceNode(node.id, point.id); + graph = graph.replace(target); + removeNode = node; + break; + } + + graph = graph.remove(removeNode); + }); return graph; }; diff --git a/modules/actions/orthogonalize.js b/modules/actions/orthogonalize.js index b3c311b33..31c532d00 100644 --- a/modules/actions/orthogonalize.js +++ b/modules/actions/orthogonalize.js @@ -1,6 +1,6 @@ import _ from 'lodash'; import { actionDeleteNode } from './delete_node'; -import { geoEuclideanDistance } from '../geo/index'; +import { geoEuclideanDistance, geoInterp } from '../geo/index'; /* * Based on https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/potlatch2/tools/Quadrilateralise.as @@ -11,26 +11,30 @@ export function actionOrthogonalize(wayId, projection) { upperThreshold = Math.cos(threshold * Math.PI / 180); - var action = function(graph) { + var action = function(graph, t) { + if (t === null || !isFinite(t)) t = 1; + t = Math.min(Math.max(+t, 0), 1); + var way = graph.entity(wayId), nodes = graph.childNodes(way), points = _.uniq(nodes).map(function(n) { return projection(n.loc); }), corner = {i: 0, dotp: 1}, epsilon = 1e-4, - i, j, score, motions; + node, loc, score, motions, i, j; - if (nodes.length === 4) { + if (points.length === 3) { // move only one vertex for right triangle for (i = 0; i < 1000; i++) { motions = points.map(calcMotion); - points[corner.i] = addPoints(points[corner.i],motions[corner.i]); + points[corner.i] = addPoints(points[corner.i], motions[corner.i]); score = corner.dotp; if (score < epsilon) { break; } } - graph = graph.replace(graph.entity(nodes[corner.i].id) - .move(projection.invert(points[corner.i]))); + node = graph.entity(nodes[corner.i].id); + loc = projection.invert(points[corner.i]); + graph = graph.replace(node.move(geoInterp(node.loc, loc, t))); } else { var best, @@ -57,25 +61,25 @@ export function actionOrthogonalize(wayId, projection) { for (i = 0; i < points.length; i++) { // only move the points that actually moved if (originalPoints[i][0] !== points[i][0] || originalPoints[i][1] !== points[i][1]) { - graph = graph.replace(graph.entity(nodes[i].id) - .move(projection.invert(points[i]))); + loc = projection.invert(points[i]); + node = graph.entity(nodes[i].id); + graph = graph.replace(node.move(geoInterp(node.loc, loc, t))); } } // remove empty nodes on straight sections - for (i = 0; i < points.length; i++) { - var node = nodes[i]; + for (i = 0; t === 1 && i < points.length; i++) { + node = graph.entity(nodes[i].id); if (graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags()) { - continue; } var dotp = normalizedDotProduct(i, points); if (dotp < -1 + epsilon) { - graph = actionDeleteNode(nodes[i].id)(graph); + graph = actionDeleteNode(node.id)(graph); } } } @@ -182,5 +186,8 @@ export function actionOrthogonalize(wayId, projection) { }; + action.transitionable = true; + + return action; } diff --git a/modules/actions/reflect.js b/modules/actions/reflect.js index 6382f83c8..a28ea7d2d 100644 --- a/modules/actions/reflect.js +++ b/modules/actions/reflect.js @@ -6,6 +6,7 @@ import { import { geoEuclideanDistance, geoExtent, + geoInterp, geoRotate } from '../geo'; @@ -52,7 +53,10 @@ export function actionReflect(reflectIds, projection) { } - var action = function(graph) { + var action = function(graph, t) { + if (t === null || !isFinite(t)) t = 1; + t = Math.min(Math.max(+t, 0), 1); + var nodes = utilGetAllNodes(reflectIds, graph), ssr = getSmallestSurroundingRectangle(graph, nodes); @@ -87,7 +91,8 @@ export function actionReflect(reflectIds, projection) { a * (c[0] - p[0]) + b * (c[1] - p[1]) + p[0], b * (c[0] - p[0]) - a * (c[1] - p[1]) + p[1] ]; - node = node.move(projection.invert(c2)); + var loc2 = projection.invert(c2); + node = node.move(geoInterp(node.loc, loc2, t)); graph = graph.replace(node); } @@ -102,5 +107,8 @@ export function actionReflect(reflectIds, projection) { }; + action.transitionable = true; + + return action; } diff --git a/modules/actions/straighten.js b/modules/actions/straighten.js index 60a49416e..43765865f 100644 --- a/modules/actions/straighten.js +++ b/modules/actions/straighten.js @@ -1,5 +1,5 @@ import { actionDeleteNode } from './delete_node'; -import { geoEuclideanDistance } from '../geo/index'; +import { geoEuclideanDistance, geoInterp } from '../geo/index'; /* @@ -8,12 +8,15 @@ import { geoEuclideanDistance } from '../geo/index'; export function actionStraighten(wayId, projection) { function positionAlongWay(n, s, e) { - return ((n[0] - s[0]) * (e[0] - s[0]) + (n[1] - s[1]) * (e[1] - s[1]))/ + return ((n[0] - s[0]) * (e[0] - s[0]) + (n[1] - s[1]) * (e[1] - s[1])) / (Math.pow(e[0] - s[0], 2) + Math.pow(e[1] - s[1], 2)); } - var action = function(graph) { + var action = function(graph, t) { + if (t === null || !isFinite(t)) t = 1; + t = Math.min(Math.max(+t, 0), 1); + var way = graph.entity(wayId), nodes = graph.childNodes(way), points = nodes.map(function(n) { return projection(n.loc); }), @@ -26,17 +29,18 @@ export function actionStraighten(wayId, projection) { var node = nodes[i], point = points[i]; - if (graph.parentWays(node).length > 1 || + if (t < 1 || graph.parentWays(node).length > 1 || graph.parentRelations(node).length || node.hasInterestingTags()) { var u = positionAlongWay(point, startPoint, endPoint), - p0 = startPoint[0] + u * (endPoint[0] - startPoint[0]), - p1 = startPoint[1] + u * (endPoint[1] - startPoint[1]); + p = [ + startPoint[0] + u * (endPoint[0] - startPoint[0]), + startPoint[1] + u * (endPoint[1] - startPoint[1]) + ], + loc2 = projection.invert(p); - graph = graph - .replace(graph.entity(node.id) - .move(projection.invert([p0, p1]))); + graph = graph.replace(node.move(geoInterp(node.loc, loc2, t))); } else { // safe to delete @@ -83,5 +87,8 @@ export function actionStraighten(wayId, projection) { }; + action.transitionable = true; + + return action; } diff --git a/modules/behavior/select.js b/modules/behavior/select.js index cf5c8d139..9c627701d 100644 --- a/modules/behavior/select.js +++ b/modules/behavior/select.js @@ -27,7 +27,9 @@ export function behaviorSelect(context) { lasso = d3.select('#surface .lasso').node(), mode = context.mode(); - if (!(datum instanceof osmEntity)) { + if (datum.type === 'midpoint') { + // do nothing + } else if (!(datum instanceof osmEntity)) { if (!d3.event.shiftKey && !lasso && mode.id !== 'browse') context.enter(modeBrowse(context)); diff --git a/modules/core/history.js b/modules/core/history.js index 212f3349e..09f2279b7 100644 --- a/modules/core/history.js +++ b/modules/core/history.js @@ -11,13 +11,15 @@ import { utilRebind } from '../util/rebind'; export function coreHistory(context) { - var stack, index, tree, - imageryUsed = ['Bing'], + var imageryUsed = ['Bing'], dispatch = d3.dispatch('change', 'undone', 'redone'), - lock = utilSessionMutex('lock'); + lock = utilSessionMutex('lock'), + duration = 150, + stack, index, tree; - function perform(actions) { + // internal _act, accepts list of actions and eased time + function _act(actions, t) { actions = Array.prototype.slice.call(actions); var annotation; @@ -31,7 +33,7 @@ export function coreHistory(context) { var graph = stack[index].graph; for (var i = 0; i < actions.length; i++) { - graph = actions[i](graph); + graph = actions[i](graph, t); } return { @@ -42,6 +44,40 @@ export function coreHistory(context) { } + // internal _perform with eased time + function _perform(args, t) { + var previous = stack[index].graph; + stack = stack.slice(0, index + 1); + stack.push(_act(args, t)); + index++; + return change(previous); + } + + + // internal _replace with eased time + function _replace(args, t) { + var previous = stack[index].graph; + // assert(index == stack.length - 1) + stack[index] = _act(args, t); + return change(previous); + } + + + // internal _overwrite with eased time + function _overwrite(args, t) { + var previous = stack[index].graph; + if (index > 0) { + index--; + stack.pop(); + } + stack = stack.slice(0, index + 1); + stack.push(_act(args, t)); + index++; + return change(previous); + } + + + // determine diffrence and dispatch a change event function change(previous) { var difference = coreDifference(previous, history.graph()); dispatch.call('change', this, difference); @@ -76,29 +112,58 @@ export function coreHistory(context) { perform: function() { - var previous = stack[index].graph; + // complete any transition already in progress + d3.select(document).interrupt('history.perform'); - stack = stack.slice(0, index + 1); - stack.push(perform(arguments)); - index++; + var transitionable = false, + action0 = arguments[0]; - return change(previous); + if (arguments.length === 1 || + arguments.length === 2 && !_.isFunction(arguments[1])) { + transitionable = !!action0.transitionable; + } + + if (transitionable) { + var origArguments = arguments; + d3.select(document) + .transition('history.perform') + .duration(duration) + .ease(d3.easeLinear) + .tween('history.tween', function() { + return function(t) { + if (t < 1) _overwrite([action0], t); + }; + }) + .on('start', function() { + _perform([action0], 0); + }) + .on('end interrupt', function() { + _overwrite(origArguments, 1); + }); + + } else { + return _perform(arguments); + } }, replace: function() { - var previous = stack[index].graph; + d3.select(document).interrupt('history.perform'); + return _replace(arguments, 1); + }, - // assert(index == stack.length - 1) - stack[index] = perform(arguments); - return change(previous); + // Same as calling pop and then perform + overwrite: function() { + d3.select(document).interrupt('history.perform'); + return _overwrite(arguments, 1); }, pop: function() { - var previous = stack[index].graph; + d3.select(document).interrupt('history.perform'); + var previous = stack[index].graph; if (index > 0) { index--; stack.pop(); @@ -107,23 +172,9 @@ export function coreHistory(context) { }, - // Same as calling pop and then perform - overwrite: function() { - var previous = stack[index].graph; - - if (index > 0) { - index--; - stack.pop(); - } - stack = stack.slice(0, index + 1); - stack.push(perform(arguments)); - index++; - - return change(previous); - }, - - undo: function() { + d3.select(document).interrupt('history.perform'); + var previous = stack[index].graph; // Pop to the next annotated state. @@ -138,8 +189,9 @@ export function coreHistory(context) { redo: function() { - var previous = stack[index].graph; + d3.select(document).interrupt('history.perform'); + var previous = stack[index].graph; while (index < stack.length - 1) { index++; if (stack[index].annotation) break; diff --git a/modules/modes/select.js b/modules/modes/select.js index a4d2dc7a6..27032262e 100644 --- a/modules/modes/select.js +++ b/modules/modes/select.js @@ -235,16 +235,21 @@ export function modeSelect(context, selectedIDs) { if (datum instanceof osmWay && !target.classed('fill')) { var choice = geoChooseEdge(context.childNodes(datum), context.mouse(), context.projection), - node = osmNode(); - - var prev = datum.nodes[choice.index - 1], + prev = datum.nodes[choice.index - 1], next = datum.nodes[choice.index]; context.perform( - actionAddMidpoint({loc: choice.loc, edge: [prev, next]}, node), + actionAddMidpoint({loc: choice.loc, edge: [prev, next]}, osmNode()), t('operations.add.annotation.vertex') ); + d3.event.preventDefault(); + d3.event.stopPropagation(); + } else if (datum.type === 'midpoint') { + context.perform( + actionAddMidpoint({loc: datum.loc, edge: datum.edge}, osmNode()), + t('operations.add.annotation.vertex')); + d3.event.preventDefault(); d3.event.stopPropagation(); } diff --git a/modules/operations/merge.js b/modules/operations/merge.js index d58a9a8bf..c55e3c350 100644 --- a/modules/operations/merge.js +++ b/modules/operations/merge.js @@ -27,7 +27,10 @@ export function operationMerge(selectedIDs, context) { } context.perform(action, annotation); - var ids = selectedIDs.filter(function(id) { return context.hasEntity(id); }); + var ids = selectedIDs.filter(function(id) { + var entity = context.hasEntity(id); + return entity && entity.type !== 'node'; + }); context.enter(modeSelect(context, ids).suppressMenu(true)); }; diff --git a/modules/services/osm.js b/modules/services/osm.js index defbf6ec1..39cefbee7 100644 --- a/modules/services/osm.js +++ b/modules/services/osm.js @@ -8,7 +8,6 @@ import { osmEntity, osmNode, osmRelation, osmWay } from '../osm/index'; import { utilDetect } from '../util/detect'; import { utilRebind } from '../util/rebind'; - var dispatch = d3.dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded'), useHttps = window.location.protocol === 'https:', protocol = useHttps ? 'https:' : 'http:', @@ -70,6 +69,7 @@ function getTags(obj) { var attrs = elems[i].attributes; tags[attrs.k.value] = attrs.v.value; } + return tags; } diff --git a/modules/svg/labels.js b/modules/svg/labels.js index 88adf4906..84b8026a7 100644 --- a/modules/svg/labels.js +++ b/modules/svg/labels.js @@ -1,6 +1,7 @@ import * as d3 from 'd3'; import _ from 'lodash'; import rbush from 'rbush'; + import { geoExtent, geoEuclideanDistance, @@ -8,9 +9,15 @@ import { geoPolygonIntersectsPolygon, geoPathLength } from '../geo/index'; + import { osmEntity } from '../osm/index'; import { utilDetect } from '../util/detect'; -import { utilDisplayName, utilEntitySelector } from '../util/index'; + +import { + utilDisplayName, + utilDisplayNameForPath, + utilEntitySelector +} from '../util/index'; export function svgLabels(projection, context) { @@ -134,7 +141,7 @@ export function svgLabels(projection, context) { .data(entities, osmEntity.key) .attr('startOffset', '50%') .attr('xlink:href', function(d) { return '#labelpath-' + d.id; }) - .text(utilDisplayName); + .text(utilDisplayNameForPath); } @@ -313,9 +320,11 @@ export function svgLabels(projection, context) { entity = labelable[k][i]; geometry = entity.geometry(graph); - var name = utilDisplayName(entity), + var getName = (geometry === 'line') ? utilDisplayNameForPath : utilDisplayName, + name = getName(entity), width = name && textWidth(name, fontSize), p; + if (geometry === 'point') { p = getPointLabel(entity, width, fontSize, geometry); } else if (geometry === 'vertex' && !lowZoom) { diff --git a/modules/ui/entity_editor.js b/modules/ui/entity_editor.js index ad10e4768..86072765a 100644 --- a/modules/ui/entity_editor.js +++ b/modules/ui/entity_editor.js @@ -13,7 +13,6 @@ import { uiTagReference } from './tag_reference'; import { uiPreset } from './preset'; import { utilRebind } from '../util/rebind'; - export function uiEntityEditor(context) { var dispatch = d3.dispatch('choose'), state = 'select', diff --git a/modules/ui/fields/address.js b/modules/ui/fields/address.js index 64c2cb48b..c3eb7f4f2 100644 --- a/modules/ui/fields/address.js +++ b/modules/ui/fields/address.js @@ -159,12 +159,13 @@ export function uiFieldAddress(field, context) { .style('width', function (d) { return d.width * 100 + '%'; }); // Update - // setup dropdowns for common address tags var addrTags = [ - 'street', 'city', 'state', 'province', 'district', - 'subdistrict', 'suburb', 'place', 'postcode' + 'city', 'county', 'country', 'district', 'hamlet', + 'neighbourhood', 'place', 'postcode', 'province', + 'quarter', 'state', 'street', 'subdistrict', 'suburb' ]; + // If fields exist for any of these tags, create dropdowns to pick nearby values.. addrTags.forEach(function(tag) { var nearValues = (tag === 'street') ? getNearStreets : (tag === 'city') ? getNearCities diff --git a/modules/util/index.js b/modules/util/index.js index e84dc86db..66847ad5f 100644 --- a/modules/util/index.js +++ b/modules/util/index.js @@ -3,6 +3,7 @@ export { utilEntitySelector } from './util'; export { utilEntityOrMemberSelector } from './util'; export { utilGetAllNodes } from './util'; export { utilDisplayName } from './util'; +export { utilDisplayNameForPath } from './util'; export { utilDisplayType } from './util'; export { utilStringQs } from './util'; export { utilQsString } from './util'; diff --git a/modules/util/svg_paths_arabic_fix.js b/modules/util/svg_paths_arabic_fix.js new file mode 100644 index 000000000..11a4c6e08 --- /dev/null +++ b/modules/util/svg_paths_arabic_fix.js @@ -0,0 +1,122 @@ +// see https://github.com/openstreetmap/iD/pull/3707 +// https://gist.github.com/mapmeld/556b09ddec07a2044c76e1ef45f01c60 + +var chars = { + // madda above alef + 1570: { initial: 'آ‎', isolated: 'ﺁ', medial: 'ﺁ', final: 'ﺂ' }, + + // hamza above and below alef + 1571: { initial: 'أ', isolated: 'ﺃ', medial: '', final: 'ﺄ' }, + // 1572 is ؤ + 1573: { initial: 'إ', isolated: 'ﺇ', medial: '', final: 'ﺈ' }, + // 1574 is ئ + 1575: { initial: 'ا', isolated: 'ا', medial: '', final: 'ﺎ' }, + 1576: { initial: 'ﺑ', isolated: 'ﺏ', medial: 'ﺒ', final: 'ﺐ' }, + + // 1577 ة + 1577: { initial: '', isolated: 'ة', medial: '', final: 'ﺔ' }, + + 1578: { initial: 'ﺗ', isolated: 'ﺕ', medial: 'ﺘ', final: 'ﺖ' }, + 1579: { initial: 'ﺛ', isolated: 'ﺙ', medial: 'ﺜ', final: 'ﺚ' }, + 1580: { initial: 'ﺟ', isolated: 'ﺝ', medial: 'ﺠ', final: 'ﺞ' }, + 1581: { initial: 'ﺣ', isolated: 'ﺡ', medial: 'ﺤ', final: 'ﺢ' }, + 1582: { initial: 'ﺧ', isolated: 'ﺥ', medial: 'ﺨ', final: 'ﺦ' }, + 1583: { initial: 'ﺩ', isolated: 'ﺩ', medial: '', final: 'ﺪ' }, + 1584: { initial: 'ﺫ', isolated: 'ﺫ', medial: '', final: 'ﺬ' }, + 1585: { initial: 'ﺭ', isolated: 'ﺭ', medial: '', final: 'ﺮ' }, + 1586: { initial: 'ﺯ', isolated: 'ﺯ', medial: '', final: 'ﺰ' }, + 1688: { initial: 'ﮊ', isolated: 'ﮊ', medial: '', final: 'ﮋ' }, + 1587: { initial: 'ﺳ', isolated: 'ﺱ', medial: 'ﺴ', final: 'ﺲ' }, + 1588: { initial: 'ﺷ', isolated: 'ﺵ', medial: 'ﺸ', final: 'ﺶ' }, + 1589: { initial: 'ﺻ', isolated: 'ﺹ', medial: 'ﺼ', final: 'ﺺ' }, + 1590: { initial: 'ﺿ', isolated: 'ﺽ', medial: 'ﻀ', final: 'ﺾ' }, + 1591: { initial: 'ﻃ', isolated: 'ﻁ', medial: 'ﻄ', final: 'ﻂ' }, + 1592: { initial: 'ﻇ', isolated: 'ﻅ', medial: 'ﻈ', final: 'ﻆ' }, + 1593: { initial: 'ﻋ', isolated: 'ﻉ', medial: 'ﻌ', final: 'ﻊ' }, + 1594: { initial: 'ﻏ', isolated: 'ﻍ', medial: 'ﻐ', final: 'ﻎ' }, + + // 1595 ػ - may be very rare + + 1601: { initial: 'ﻓ', isolated: 'ﻑ', medial: 'ﻔ', final: 'ﻒ' }, + 1602: { initial: 'ﻗ', isolated: 'ﻕ', medial: 'ﻘ', final: 'ﻖ' }, + 1604: { initial: 'ﻟ', isolated: 'ﻝ', medial: 'ﻠ', final: 'ﻞ' }, + 1605: { initial: 'ﻣ', isolated: 'ﻡ', medial: 'ﻤ', final: 'ﻢ' }, + 1606: { initial: 'ﻧ', isolated: 'ﻥ', medial: 'ﻨ', final: 'ﻦ' }, + 1607: { initial: 'ﻫ', isolated: 'ﻩ', medial: 'ﻬ', final: 'ﻪ' }, + 1608: { initial: 'ﻭ', isolated: 'ﻭ', medial: '', final: 'ﻮ' }, + + // 1609 ى + 1609: { initial: 'ﯨ', isolated: 'ﻯ', medial: 'ﯩ', final: 'ﻰ' }, + // 1610 ي + 1610: { initial: 'ﻳ', isolated: 'ﻱ', medial: 'ﻴ', final: 'ﻲ' }, + + // short vowel sounds / tashkil markings + + 1662: { initial: 'ﭘ', isolated: 'ﭖ', medial: 'ﭙ', final: 'ﭗ' }, + + 1670: { initial: 'ﭼ', isolated: 'ﭺ', medial: 'ﭽ', final: 'ﭻ' }, + 1603: { initial: 'ﻛ', isolated: 'ﻙ', medial: 'ﻜ', final: 'ﻚ' }, + 1705: { initial: 'ﻛ', isolated: 'ﮎ', medial: 'ﻜ', final: 'ﮏ' }, + 1711: { initial: 'ﮔ', isolated: 'ﮒ', medial: 'ﮕ', final: 'ﮓ' }, + 1740: { initial: 'ﻳ', isolated: 'ﻯ', medial: 'ﻴ', final: 'ﻰ' }, + 5000: { initial: 'ﻻ', isolated: 'ﻻ', medial: '', final: 'ﻼ' } +}; + + +export function fixArabicScriptTextForSvg(inputText) { + var context = true; + var ret = ''; + var rtlBuffer = []; + + for (var i = 0, l = inputText.length; i < l; i++) { + var code = inputText[i].charCodeAt(0); + var nextCode = inputText[i + 1] ? inputText[i + 1].charCodeAt(0) : 0; + + if (!chars[code]) { + if (code === 32 && rtlBuffer.length) { + // whitespace + rtlBuffer = [rtlBuffer.reverse().join('') + ' ']; + } else { + // non-RTL character + ret += rtlBuffer.reverse().join('') + inputText[i]; + rtlBuffer = []; + } + continue; + } + if (context) { + if (i === l - 1 || nextCode === 32) { + rtlBuffer.push(chars[code].isolated); + } else { + // special case for لا + if (code === 1604 && nextCode === 1575) { + rtlBuffer.push(chars[5000].initial); + i++; + context = true; + continue; + } + rtlBuffer.push(chars[code].initial); + } + } else { + if (i === l - 1 || nextCode === 32){ + rtlBuffer.push(chars[code].final); + } else { + // special case for ﻼ + if (code === 1604 && nextCode === 1575){ + rtlBuffer.push(chars[5000].final); + i++; + context = true; + continue; + } + if (chars[code].medial === ''){ + rtlBuffer.push(chars[code].final); + } else { + rtlBuffer.push(chars[code].medial); + } + } + } + context = (chars[code].medial === '') || nextCode === 32; + } + + ret += rtlBuffer.reverse().join(''); + return ret; +} diff --git a/modules/util/util.js b/modules/util/util.js index 3302a3f56..229d0d14b 100644 --- a/modules/util/util.js +++ b/modules/util/util.js @@ -2,6 +2,7 @@ import * as d3 from 'd3'; import { t, textDirection } from './locale'; import { utilDetect } from './detect'; import { remove as removeDiacritics } from 'diacritics'; +import { fixArabicScriptTextForSvg } from './svg_paths_arabic_fix'; export function utilTagText(entity) { @@ -60,12 +61,27 @@ export function utilDisplayName(entity) { var localizedNameKey = 'name:' + utilDetect().locale.toLowerCase().split('-')[0], name = entity.tags[localizedNameKey] || entity.tags.name || '', network = entity.tags.cycle_network || entity.tags.network; + if (!name && entity.tags.ref) { name = entity.tags.ref; if (network) { name = network + ' ' + name; } } + + return name; +} + + +export function utilDisplayNameForPath(entity) { + var name = utilDisplayName(entity); + var isFirefox = utilDetect().browser.toLowerCase().indexOf('firefox') > -1; + var arabicRegex = /[\u0600-\u06FF]/g; + + if (!isFirefox && name && arabicRegex.test(name)) { + name = fixArabicScriptTextForSvg(name); + } + return name; } diff --git a/package.json b/package.json index 55e2b4645..cf2362145 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ }, "dependencies": { "diacritics": "1.2.3", - "lodash": "4.17.2", + "lodash": "4.17.4", "marked": "0.3.6", "osm-auth": "1.0.1", "rbush": "2.0.1", @@ -40,11 +40,11 @@ "devDependencies": { "brfs": "1.4.3", "chai": "~3.5.0", - "d3": "4.4.0", + "d3": "4.4.1", "ecstatic": "~2.1.0", "editor-layer-index": "osmlab/editor-layer-index.git#gh-pages", "gaze": "~1.1.1", - "eslint": "~3.12.0", + "eslint": "~3.13.0", "glob": "~7.1.0", "happen": "~0.3.1", "js-yaml": "~3.7.0", @@ -56,11 +56,11 @@ "mocha": "~3.2.0", "mocha-phantomjs-core": "~2.1.0", "name-suggestion-index": "0.1.1", - "npm-run-all": "~3.1.1", + "npm-run-all": "~4.0.0", "phantomjs-prebuilt": "~2.1.11", "request": "~2.79.0", - "rollup": "0.38.0", - "rollup-plugin-commonjs": "6.0.1", + "rollup": "0.41.1", + "rollup-plugin-commonjs": "7.0.0", "rollup-plugin-json": "2.0.2", "rollup-plugin-node-resolve": "2.0.0", "shelljs": "~0.7.5", diff --git a/test/spec/actions/circularize.js b/test/spec/actions/circularize.js index 7d598f9ed..4c70bc4e2 100644 --- a/test/spec/actions/circularize.js +++ b/test/spec/actions/circularize.js @@ -271,4 +271,53 @@ describe('iD.actionCircularize', function () { expect(isCircular('-', graph)).to.be.ok; }); + + describe('transitions', function () { + it('is transitionable', function() { + expect(iD.actionCircularize().transitionable).to.be.true; + }); + + it('circularize at t = 0', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [2, 0]}), + iD.Node({id: 'c', loc: [2, 2]}), + iD.Node({id: 'd', loc: [0, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionCircularize('-', projection)(graph, 0); + expect(isCircular('-', graph)).to.be.not.ok; + expect(graph.entity('-').nodes).to.have.length(20); + expect(area('-', graph)).to.be.closeTo(-4, 1e-2); + }); + + it('circularize at t = 0.5', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [2, 0]}), + iD.Node({id: 'c', loc: [2, 2]}), + iD.Node({id: 'd', loc: [0, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionCircularize('-', projection)(graph, 0.5); + expect(isCircular('-', graph)).to.be.not.ok; + expect(graph.entity('-').nodes).to.have.length(20); + expect(area('-', graph)).to.be.closeTo(-4.812, 1e-2); + }); + + it('circularize at t = 1', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [2, 0]}), + iD.Node({id: 'c', loc: [2, 2]}), + iD.Node({id: 'd', loc: [0, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionCircularize('-', projection)(graph, 1); + expect(isCircular('-', graph)).to.be.ok; + expect(graph.entity('-').nodes).to.have.length(20); + expect(area('-', graph)).to.be.closeTo(-6.168, 1e-2); + }); + }); + }); diff --git a/test/spec/actions/merge.js b/test/spec/actions/merge.js index 6b23ed9a8..dc3d27059 100644 --- a/test/spec/actions/merge.js +++ b/test/spec/actions/merge.js @@ -36,4 +36,23 @@ describe('iD.actionMerge', function () { expect(graph.entity('w').tags).to.eql({a: 'a', b: 'b', area: 'yes'}); expect(graph.entity('r').members).to.eql([{id: 'w', role: 'r', type: 'way'}]); }); + + it('preserves original point if possible', function () { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [1, 0], tags: {a: 'a'}}), + iD.Node({id: 'p', loc: [0, 0], tags: {p: 'p'}}), + iD.Node({id: 'q', loc: [0, 1]}), + iD.Way({id: 'w', nodes: ['p', 'q'], tags: {w: 'w'}}) + ]), + action = iD.actionMerge(['a', 'w']); + + graph = action(graph); + expect(graph.hasEntity('a')).to.be.ok; + expect(graph.hasEntity('p')).to.be.ok; + expect(graph.hasEntity('q')).to.be.undefined; + expect(graph.entity('w').tags).to.eql({a: 'a', w: 'w'}); + expect(graph.entity('w').nodes).to.eql(['p', 'a']); + expect(graph.entity('a').loc[0]).to.eql(0); + expect(graph.entity('a').loc[1]).to.eql(1); + }); }); diff --git a/test/spec/actions/orthogonalize.js b/test/spec/actions/orthogonalize.js index 2c8138977..d3fb8946d 100644 --- a/test/spec/actions/orthogonalize.js +++ b/test/spec/actions/orthogonalize.js @@ -11,7 +11,6 @@ describe('iD.actionOrthogonalize', function () { ]); graph = iD.actionOrthogonalize('-', projection)(graph); - expect(graph.entity('-').nodes).to.have.length(5); }); @@ -25,7 +24,6 @@ describe('iD.actionOrthogonalize', function () { ]); graph = iD.actionOrthogonalize('-', projection)(graph); - expect(graph.entity('-').nodes).to.have.length(5); }); @@ -38,7 +36,6 @@ describe('iD.actionOrthogonalize', function () { ]); graph = iD.actionOrthogonalize('-', projection)(graph); - expect(graph.entity('-').nodes).to.have.length(4); }); @@ -53,7 +50,6 @@ describe('iD.actionOrthogonalize', function () { ]); graph = iD.actionOrthogonalize('-', projection)(graph); - expect(graph.hasEntity('d')).to.eq(undefined); }); @@ -68,7 +64,6 @@ describe('iD.actionOrthogonalize', function () { ]); graph = iD.actionOrthogonalize('-', projection)(graph); - expect(graph.entity('-').nodes).to.have.length(6); expect(graph.hasEntity('d')).to.not.eq(undefined); }); @@ -121,4 +116,69 @@ describe('iD.actionOrthogonalize', function () { expect(Object.keys(diff.changes()).sort()).to.eql(['a', 'b', 'c', 'f']); }); + + + describe('transitions', function () { + it('is transitionable', function() { + expect(iD.actionOrthogonalize().transitionable).to.be.true; + }); + + it('orthogonalize at t = 0', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Node({id: 'e', loc: [3, 1]}), + iD.Node({id: 'f', loc: [0, 1]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']}) + ]); + + graph = iD.actionOrthogonalize('-', projection)(graph, 0); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'd', 'e', 'f', 'a']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0.01, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(-0.01, 1e-6); + + }); + + it('orthogonalize at t = 0.5', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Node({id: 'e', loc: [3, 1]}), + iD.Node({id: 'f', loc: [0, 1]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']}) + ]); + + graph = iD.actionOrthogonalize('-', projection)(graph, 0.5); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'd', 'e', 'f', 'a']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-3); + expect(graph.entity('b').loc[1]).to.be.closeTo(0.005, 1e-3); + expect(graph.entity('c').loc[0]).to.be.closeTo(2, 1e-3); + expect(graph.entity('c').loc[1]).to.be.closeTo(-0.005, 1e-3); + }); + + it('orthogonalize at t = 1', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Node({id: 'e', loc: [3, 1]}), + iD.Node({id: 'f', loc: [0, 1]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'e', 'f', 'a']}) + ]); + + graph = iD.actionOrthogonalize('-', projection)(graph, 1); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'd', 'e', 'f', 'a']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 2e-3); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 2e-3); + expect(graph.hasEntity('c')).to.eq(undefined); + }); + }); + }); diff --git a/test/spec/actions/reflect.js b/test/spec/actions/reflect.js index 1a8b22061..c06737129 100644 --- a/test/spec/actions/reflect.js +++ b/test/spec/actions/reflect.js @@ -63,4 +63,125 @@ describe('iD.actionReflect', function() { expect(graph.entity('d').loc[1]).to.be.closeTo(2, 1e-6); }); + + describe('transitions', function () { + it('is transitionable', function() { + expect(iD.actionReflect().transitionable).to.be.true; + }); + + it('reflect long at t = 0', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [4, 0]}), + iD.Node({id: 'c', loc: [4, 2]}), + iD.Node({id: 'd', loc: [1, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionReflect(['-'], projection)(graph, 0); + expect(graph.entity('a').loc[0]).to.be.closeTo(0, 1e-6); + expect(graph.entity('a').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('b').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(2, 1e-6); + expect(graph.entity('d').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('d').loc[1]).to.be.closeTo(2, 1e-6); + }); + + it('reflect long at t = 0.5', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [4, 0]}), + iD.Node({id: 'c', loc: [4, 2]}), + iD.Node({id: 'd', loc: [1, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionReflect(['-'], projection)(graph, 0.5); + expect(graph.entity('a').loc[0]).to.be.closeTo(0, 1e-6); + expect(graph.entity('a').loc[1]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(1, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(1, 1e-6); + expect(graph.entity('d').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('d').loc[1]).to.be.closeTo(1, 1e-6); + }); + + it('reflect long at t = 1', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [4, 0]}), + iD.Node({id: 'c', loc: [4, 2]}), + iD.Node({id: 'd', loc: [1, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionReflect(['-'], projection)(graph, 1); + expect(graph.entity('a').loc[0]).to.be.closeTo(0, 1e-6); + expect(graph.entity('a').loc[1]).to.be.closeTo(2, 1e-6); + expect(graph.entity('b').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(2, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('d').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('d').loc[1]).to.be.closeTo(0, 1e-6); + }); + + it('reflect short at t = 0', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [4, 0]}), + iD.Node({id: 'c', loc: [4, 2]}), + iD.Node({id: 'd', loc: [1, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionReflect(['-'], projection).useLongAxis(false)(graph, 0); + expect(graph.entity('a').loc[0]).to.be.closeTo(0, 1e-6); + expect(graph.entity('a').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('b').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(2, 1e-6); + expect(graph.entity('d').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('d').loc[1]).to.be.closeTo(2, 1e-6); + }); + + it('reflect short at t = 0.5', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [4, 0]}), + iD.Node({id: 'c', loc: [4, 2]}), + iD.Node({id: 'd', loc: [1, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionReflect(['-'], projection).useLongAxis(false)(graph, 0.5); + expect(graph.entity('a').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('a').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('b').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(2, 1e-6); + expect(graph.entity('d').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('d').loc[1]).to.be.closeTo(2, 1e-6); + }); + + it('reflect short at t = 1', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [4, 0]}), + iD.Node({id: 'c', loc: [4, 2]}), + iD.Node({id: 'd', loc: [1, 2]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd', 'a']}) + ]); + graph = iD.actionReflect(['-'], projection).useLongAxis(false)(graph, 1); + expect(graph.entity('a').loc[0]).to.be.closeTo(4, 1e-6); + expect(graph.entity('a').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('b').loc[0]).to.be.closeTo(0, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(0, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(2, 1e-6); + expect(graph.entity('d').loc[0]).to.be.closeTo(3, 1e-6); + expect(graph.entity('d').loc[1]).to.be.closeTo(2, 1e-6); + }); + + }); }); diff --git a/test/spec/actions/straighten.js b/test/spec/actions/straighten.js index 93097f44d..e9c63ed4f 100644 --- a/test/spec/actions/straighten.js +++ b/test/spec/actions/straighten.js @@ -5,12 +5,11 @@ describe('iD.actionStraighten', function () { it('returns falsy for ways with internal nodes near centerline', function () { var graph = iD.Graph([ iD.Node({id: 'a', loc: [0, 0]}), - iD.Node({id: 'b', loc: [1, 0.1]}), + iD.Node({id: 'b', loc: [1, 0.01]}), iD.Node({id: 'c', loc: [2, 0]}), iD.Node({id: 'd', loc: [3, 0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}) ]); - expect(iD.actionStraighten('-', projection).disabled(graph)).not.to.be.ok; }); @@ -22,7 +21,6 @@ describe('iD.actionStraighten', function () { iD.Node({id: 'd', loc: [3, 0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}) ]); - expect(iD.actionStraighten('-', projection).disabled(graph)).to.equal('too_bendy'); }); @@ -34,49 +32,108 @@ describe('iD.actionStraighten', function () { iD.Node({id: 'd', loc: [0, 0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}) ]); - expect(iD.actionStraighten('-', projection).disabled(graph)).to.equal('too_bendy'); }); }); + it('deletes empty nodes', function() { var graph = iD.Graph([ iD.Node({id: 'a', loc: [0, 0]}), - iD.Node({id: 'b', loc: [2, 0], tags: {}}), - iD.Node({id: 'c', loc: [2, 2]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {}}), + iD.Node({id: 'c', loc: [2, 0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}) ]); graph = iD.actionStraighten('-', projection)(graph); - + expect(graph.entity('-').nodes).to.eql(['a', 'c']); expect(graph.hasEntity('b')).to.eq(undefined); }); it('does not delete tagged nodes', function() { var graph = iD.Graph([ iD.Node({id: 'a', loc: [0, 0]}), - iD.Node({id: 'b', loc: [2, 0], tags: {foo: 'bar'}}), - iD.Node({id: 'c', loc: [2, 2]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, 0]}), iD.Way({id: '-', nodes: ['a', 'b', 'c']}) ]); graph = iD.actionStraighten('-', projection)(graph); - expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); }); it('does not delete nodes connected to other ways', function() { var graph = iD.Graph([ iD.Node({id: 'a', loc: [0, 0]}), - iD.Node({id: 'b', loc: [2, 0]}), - iD.Node({id: 'c', loc: [2, 2]}), - iD.Node({id: 'd', loc: [0, 2]}), - iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}), + iD.Node({id: 'b', loc: [1, 0.01]}), + iD.Node({id: 'c', loc: [2, 0]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c']}), iD.Way({id: '=', nodes: ['b']}) ]); graph = iD.actionStraighten('-', projection)(graph); - - expect(graph.entity('-').nodes).to.have.length(3); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); }); + + + describe('transitions', function () { + it('is transitionable', function() { + expect(iD.actionStraighten().transitionable).to.be.true; + }); + + it('straighten at t = 0', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}) + ]); + + graph = iD.actionStraighten('-', projection)(graph, 0); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'd']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0.01, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(-0.01, 1e-6); + }); + + it('straighten at t = 0.5', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}) + ]); + + graph = iD.actionStraighten('-', projection)(graph, 0.5); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'c', 'd']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0.005, 1e-6); + expect(graph.entity('c').loc[0]).to.be.closeTo(2, 1e-6); + expect(graph.entity('c').loc[1]).to.be.closeTo(-0.005, 1e-6); + }); + + it('straighten at t = 1', function() { + var graph = iD.Graph([ + iD.Node({id: 'a', loc: [0, 0]}), + iD.Node({id: 'b', loc: [1, 0.01], tags: {foo: 'bar'}}), + iD.Node({id: 'c', loc: [2, -0.01]}), + iD.Node({id: 'd', loc: [3, 0]}), + iD.Way({id: '-', nodes: ['a', 'b', 'c', 'd']}) + ]); + + graph = iD.actionStraighten('-', projection)(graph, 1); + expect(graph.entity('-').nodes).to.eql(['a', 'b', 'd']); + expect(graph.entity('b').loc[0]).to.be.closeTo(1, 1e-6); + expect(graph.entity('b').loc[1]).to.be.closeTo(0, 1e-6); + expect(graph.hasEntity('c')).to.eq(undefined); + }); + }); + }); diff --git a/test/spec/core/history.js b/test/spec/core/history.js index 723474153..10693c726 100644 --- a/test/spec/core/history.js +++ b/test/spec/core/history.js @@ -51,6 +51,7 @@ describe('iD.History', function () { history.on('change', spy); var difference = history.perform(action); expect(spy).to.have.been.calledWith(difference); + expect(spy.callCount).to.eql(1); }); it('performs multiple actions', function () { @@ -61,6 +62,17 @@ describe('iD.History', function () { expect(action2).to.have.been.called; expect(history.undoAnnotation()).to.equal('annotation'); }); + + it('performs transitionable actions in a transition', function (done) { + var action1 = function() { return iD.Graph(); }; + action1.transitionable = true; + history.on('change', spy); + history.perform(action1); + window.setTimeout(function() { + expect(spy.callCount).to.be.above(2); + done(); + }, 300); + }); }); describe('#replace', function () {