Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
iD-indoor
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Analytics
Analytics
Repository
Value Stream
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Commits
Open sidebar
Adrien Pavie
iD-indoor
Commits
c42bd2a4
Commit
c42bd2a4
authored
Jul 08, 2016
by
Bryan Housel
Committed by
GitHub
Jul 08, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #3236 from openstreetmap/diacritics
Replace diacritics when doing fuzzy searches
parents
ea9de79c
c2629a33
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
184 additions
and
46 deletions
+184
-46
modules/presets/collection.js
modules/presets/collection.js
+26
-23
modules/presets/preset.js
modules/presets/preset.js
+1
-1
modules/util/util.js
modules/util/util.js
+7
-0
package.json
package.json
+1
-0
test/spec/presets/collection.js
test/spec/presets/collection.js
+149
-22
No files found.
modules/presets/collection.js
View file @
c42bd2a4
import
{
editDistance
}
from
'
../util/index
'
;
export
function
Collection
(
collection
)
{
var
maxSearchResults
=
50
,
maxSuggestionResults
=
10
;
...
...
@@ -22,6 +23,20 @@ export function Collection(collection) {
search
:
function
(
value
,
geometry
)
{
if
(
!
value
)
return
this
;
function
leading
(
a
)
{
var
index
=
a
.
indexOf
(
value
);
return
index
===
0
||
a
[
index
-
1
]
===
'
'
;
}
function
suggestionName
(
name
)
{
var
nameArray
=
name
.
split
(
'
-
'
);
if
(
nameArray
.
length
>
1
)
{
name
=
nameArray
.
slice
(
0
,
nameArray
.
length
-
1
).
join
(
'
-
'
);
}
return
name
.
toLowerCase
();
}
value
=
value
.
toLowerCase
();
var
searchable
=
_
.
filter
(
collection
,
function
(
a
)
{
...
...
@@ -31,10 +46,6 @@ export function Collection(collection) {
return
a
.
suggestion
===
true
;
});
function
leading
(
a
)
{
var
index
=
a
.
indexOf
(
value
);
return
index
===
0
||
a
[
index
-
1
]
===
'
'
;
}
// matches value to preset.name
var
leading_name
=
_
.
filter
(
searchable
,
function
(
a
)
{
...
...
@@ -57,10 +68,10 @@ export function Collection(collection) {
// finds close matches to value in preset.name
var
levenstein
_name
=
searchable
.
map
(
function
(
a
)
{
var
similar
_name
=
searchable
.
map
(
function
(
a
)
{
return
{
preset
:
a
,
dist
:
editDistance
(
value
,
a
.
name
()
.
toLowerCase
()
)
dist
:
editDistance
(
value
,
a
.
name
())
};
}).
filter
(
function
(
a
)
{
return
a
.
dist
+
Math
.
min
(
value
.
length
-
a
.
preset
.
name
().
length
,
0
)
<
3
;
...
...
@@ -71,20 +82,12 @@ export function Collection(collection) {
});
// finds close matches to value in preset.terms
var
leventstein
_terms
=
_
.
filter
(
searchable
,
function
(
a
)
{
var
similar
_terms
=
_
.
filter
(
searchable
,
function
(
a
)
{
return
_
.
some
(
a
.
terms
()
||
[],
function
(
b
)
{
return
editDistance
(
value
,
b
)
+
Math
.
min
(
value
.
length
-
b
.
length
,
0
)
<
3
;
});
});
function
suggestionName
(
name
)
{
var
nameArray
=
name
.
split
(
'
-
'
);
if
(
nameArray
.
length
>
1
)
{
name
=
nameArray
.
slice
(
0
,
nameArray
.
length
-
1
).
join
(
'
-
'
);
}
return
name
.
toLowerCase
();
}
var
leading_suggestions
=
_
.
filter
(
suggestions
,
function
(
a
)
{
return
leading
(
suggestionName
(
a
.
name
()));
}).
sort
(
function
(
a
,
b
)
{
...
...
@@ -95,7 +98,7 @@ export function Collection(collection) {
else
return
i
;
});
var
leven
_suggestions
=
suggestions
.
map
(
function
(
a
)
{
var
similar
_suggestions
=
suggestions
.
map
(
function
(
a
)
{
return
{
preset
:
a
,
dist
:
editDistance
(
value
,
suggestionName
(
a
.
name
()))
...
...
@@ -111,13 +114,13 @@ export function Collection(collection) {
var
other
=
presets
.
item
(
geometry
);
var
results
=
leading_name
.
concat
(
leading_terms
,
leading_tag_values
,
leading_suggestions
.
slice
(
0
,
maxSuggestionResults
+
5
),
levenstein
_name
,
leventstein
_terms
,
leven
_suggestions
.
slice
(
0
,
maxSuggestionResults
)
).
slice
(
0
,
maxSearchResults
-
1
);
leading_terms
,
leading_tag_values
,
leading_suggestions
.
slice
(
0
,
maxSuggestionResults
+
5
),
similar
_name
,
similar
_terms
,
similar
_suggestions
.
slice
(
0
,
maxSuggestionResults
)
).
slice
(
0
,
maxSearchResults
-
1
);
return
Collection
(
_
.
uniq
(
results
.
concat
(
other
)
...
...
modules/presets/preset.js
View file @
c42bd2a4
...
...
@@ -35,7 +35,7 @@ export function Preset(id, preset, fields) {
return
t
(
'
presets.presets.
'
+
id
+
'
.
'
+
scope
,
options
);
};
var
name
=
preset
.
name
;
var
name
=
preset
.
name
||
''
;
preset
.
name
=
function
()
{
if
(
preset
.
suggestion
)
{
id
=
id
.
split
(
'
/
'
);
...
...
modules/util/util.js
View file @
c42bd2a4
import
{
remove
as
removeDiacritics
}
from
'
diacritics
'
;
export
function
tagText
(
entity
)
{
return
d3
.
entries
(
entity
.
tags
).
map
(
function
(
e
)
{
return
e
.
key
+
'
=
'
+
e
.
value
;
...
...
@@ -114,7 +116,12 @@ export function getStyle(selector) {
}
}
// Calculates Levenshtein distance between two strings
// see: https://en.wikipedia.org/wiki/Levenshtein_distance
// first converts the strings to lowercase and replaces diacritic marks with ascii equilivants.
export
function
editDistance
(
a
,
b
)
{
a
=
removeDiacritics
(
a
.
toLowerCase
());
b
=
removeDiacritics
(
b
.
toLowerCase
());
if
(
a
.
length
===
0
)
return
b
.
length
;
if
(
b
.
length
===
0
)
return
a
.
length
;
var
matrix
=
[];
...
...
package.json
View file @
c42bd2a4
...
...
@@ -22,6 +22,7 @@
],
"license"
:
"
ISC
"
,
"dependencies"
:
{
"
diacritics
"
:
"
1.2.3
"
,
"
marked
"
:
"
0.3.5
"
,
"
osm-auth
"
:
"
0.2.8
"
,
"
rbush
"
:
"
2.0.1
"
,
...
...
test/spec/presets/collection.js
View file @
c42bd2a4
/* global locale: true */
/* eslint no-console: 0 */
describe
(
'
iD.presets.Collection
'
,
function
()
{
var
p
=
{
point
:
iD
.
presets
.
Preset
(
'
point
'
,
{
name
:
'
Point
'
,
tags
:
{},
geometry
:
[
'
point
'
]
}),
line
:
iD
.
presets
.
Preset
(
'
line
'
,
{
name
:
'
Line
'
,
tags
:
{},
geometry
:
[
'
line
'
]
}),
area
:
iD
.
presets
.
Preset
(
'
area
'
,
{
name
:
'
Area
'
,
tags
:
{},
geometry
:
[
'
area
'
]
}),
residential
:
iD
.
presets
.
Preset
(
'
highway/residential
'
,
{
tags
:
{
highway
:
'
residential
'
},
geometry
:
[
'
line
'
]
grill
:
iD
.
presets
.
Preset
(
'
__test/amenity/bbq
'
,
{
name
:
'
Grill
'
,
tags
:
{
amenity
:
'
bbq
'
},
geometry
:
[
'
point
'
],
terms
:
[]
}),
sandpit
:
iD
.
presets
.
Preset
(
'
__test/amenity/grit_bin
'
,
{
name
:
'
Sandpit
'
,
tags
:
{
amenity
:
'
grit_bin
'
},
geometry
:
[
'
point
'
],
terms
:
[]
}),
residential
:
iD
.
presets
.
Preset
(
'
__test/highway/residential
'
,
{
name
:
'
Residential Area
'
,
tags
:
{
highway
:
'
residential
'
},
geometry
:
[
'
point
'
,
'
area
'
],
terms
:
[]
}),
grass1
:
iD
.
presets
.
Preset
(
'
__test/landuse/grass1
'
,
{
name
:
'
Grass
'
,
tags
:
{
landuse
:
'
grass
'
},
geometry
:
[
'
point
'
,
'
area
'
],
terms
:
[]
}),
grass2
:
iD
.
presets
.
Preset
(
'
__test/landuse/grass2
'
,
{
name
:
'
Ğṝȁß
'
,
tags
:
{
landuse
:
'
ğṝȁß
'
},
geometry
:
[
'
point
'
,
'
area
'
],
terms
:
[]
}),
park
:
iD
.
presets
.
Preset
(
'
__test/leisure/park
'
,
{
name
:
'
Park
'
,
tags
:
{
leisure
:
'
park
'
},
geometry
:
[
'
point
'
,
'
area
'
],
terms
:
[
'
grass
'
]
}),
soccer
:
iD
.
presets
.
Preset
(
'
__test/leisure/pitch/soccer
'
,
{
name
:
'
Soccer Field
'
,
tags
:
{
leisure
:
'
pitch
'
,
sport
:
'
soccer
'
},
geometry
:
[
'
point
'
,
'
area
'
],
terms
:
[
'
fußball
'
]
}),
park
:
iD
.
presets
.
Preset
(
'
leisure/park
'
,
{
tags
:
{
leisure
:
'
park
'
}
,
geometry
:
[
'
point
'
,
'
area
'
]
football
:
iD
.
presets
.
Preset
(
'
__test/leisure/pitch/american_football
'
,
{
name
:
'
Football Field
'
,
tags
:
{
leisure
:
'
pitch
'
,
sport
:
'
american_football
'
},
geometry
:
[
'
point
'
,
'
area
'
]
,
terms
:
[
'
gridiron
'
]
})
};
var
c
=
iD
.
presets
.
Collection
([
p
.
point
,
p
.
area
,
p
.
residential
,
p
.
park
]);
var
c
=
iD
.
presets
.
Collection
([
p
.
point
,
p
.
line
,
p
.
area
,
p
.
grill
,
p
.
sandpit
,
p
.
residential
,
p
.
grass1
,
p
.
grass2
,
p
.
park
,
p
.
soccer
,
p
.
football
]);
var
saved
;
// setup mock locale object..
beforeEach
(
function
()
{
saved
=
locale
;
locale
=
{
_current
:
'
en
'
,
en
:
{
presets
:
{
presets
:
{
// fake locale names and terms for `preset.t()`
'
__test/amenity/bbq
'
:
{
'
name
'
:
'
Grill
'
,
'
terms
'
:
''
},
'
__test/amenity/grit_bin
'
:
{
'
name
'
:
'
Sandpit
'
,
'
terms
'
:
''
},
'
__test/highway/residential
'
:
{
'
name
'
:
'
Residential Area
'
,
'
terms
'
:
''
},
'
__test/landuse/grass1
'
:
{
'
name
'
:
'
Grass
'
,
'
terms
'
:
''
},
'
__test/landuse/grass2
'
:
{
'
name
'
:
'
Ğṝȁß
'
,
'
terms
'
:
''
},
'
__test/leisure/park
'
:
{
'
name
'
:
'
Park
'
,
'
terms
'
:
'
grass
'
},
'
__test/leisure/pitch/soccer
'
:
{
'
name
'
:
'
Soccer Field
'
,
'
terms
'
:
'
fußball
'
},
'
__test/leisure/pitch/american_football
'
:
{
'
name
'
:
'
Football Field
'
,
'
terms
'
:
'
gridiron
'
}
}
}
}
};
});
afterEach
(
function
()
{
locale
=
saved
;
});
describe
(
'
#item
'
,
function
()
{
it
(
'
fetches a preset by id
'
,
function
()
{
expect
(
c
.
item
(
'
highway/residential
'
)).
to
.
equal
(
p
.
residential
);
expect
(
c
.
item
(
'
__test/
highway/residential
'
)).
to
.
equal
(
p
.
residential
);
});
});
describe
(
'
#matchGeometry
'
,
function
()
{
it
(
'
returns a new collection only containing presets matching a geometry
'
,
function
()
{
expect
(
c
.
matchGeometry
(
'
area
'
).
collection
).
to
.
eql
([
p
.
area
,
p
.
park
]);
expect
(
c
.
matchGeometry
(
'
area
'
).
collection
).
to
.
include
.
members
(
[
p
.
area
,
p
.
residential
,
p
.
park
,
p
.
soccer
,
p
.
football
]
);
});
});
describe
(
'
#search
'
,
function
()
{
it
(
'
filters presets by name
'
,
function
()
{
expect
(
c
.
search
(
'
resid
'
,
'
line
'
).
collection
.
indexOf
(
p
.
residential
)
>=
0
).
to
.
eql
(
true
);
it
(
'
matches leading name
'
,
function
()
{
var
col
=
c
.
search
(
'
resid
'
,
'
area
'
).
collection
;
expect
(
col
.
indexOf
(
p
.
residential
)).
to
.
eql
(
0
);
// 1. 'Residential' (by name)
});
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
(
1
);
// 2. 'Football' (leading term 'gridiron')
expect
(
col
.
indexOf
(
p
.
sandpit
)).
to
.
eql
(
2
);
// 3. 'Sandpit' (leading tag value 'grit_bin')
expect
(
col
.
indexOf
(
p
.
grass1
)).
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
)).
to
.
eql
(
5
);
// 6. 'Park' (similar term 'grass')
});
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)
});
it
(
'
is fuzzy
'
,
function
()
{
expect
(
c
.
search
(
'
rusid
'
,
'
line
'
).
collection
.
indexOf
(
p
.
residential
)
>=
0
).
to
.
eql
(
true
);
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)
});
it
(
'
includes the appropriate fallback preset
'
,
function
()
{
expect
(
c
.
search
(
'
blade of grass
'
,
'
point
'
).
collection
.
indexOf
(
p
.
point
)
>=
0
).
to
.
eql
(
true
);
expect
(
c
.
search
(
'
blade of grass
'
,
'
area
'
).
collection
.
indexOf
(
p
.
area
)
>=
0
).
to
.
eql
(
true
);
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
);
});
it
(
'
excludes presets with searchable: false
'
,
function
()
{
var
excluded
=
iD
.
presets
.
Preset
(
'
excluded
'
,
{
tags
:
{},
geometry
:
[],
var
excluded
=
iD
.
presets
.
Preset
(
'
__test/excluded
'
,
{
name
:
'
excluded
'
,
tags
:
{
amenity
:
'
excluded
'
},
geometry
:
[
'
point
'
],
searchable
:
false
}),
collection
=
iD
.
presets
.
Collection
([
excluded
,
p
.
point
]);
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment