-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Name-suggestion-index v6 #8305
Merged
Merged
Name-suggestion-index v6 #8305
Changes from 13 commits
Commits
Show all changes
58 commits
Select commit
Hold shift + click to select a range
b0800c1
Update to name-suggestion-index v5
bhousel 9eb6f87
Update presetIndex to resolve and index locationSets
bhousel 0ad7de0
Move the location index and resolver into a global coreLocations
bhousel ab85590
Pre-resolve the world locationSet
bhousel 271e1c2
Use locationManager to filter fields/presets/defaults
bhousel c4daf1b
Use locationManager to resolve/query community index
bhousel 868db88
Have mergeLocationSets work on Objects, add locationSetID property
bhousel 2d8c907
coreLocation tests, documentation
bhousel d3fb8c6
Upgrade validations outdated_tags, suspicious_names to NSI v5
bhousel 0b3795c
NSI match returns an object now
bhousel a38a93c
Actually assign the locationSetID properties on the given objects
bhousel f39f73e
Make sure suggestion preset terms get used in the search
bhousel 4228b65
Support more `*:wikidata` tags for field locking and pin styling
bhousel 74d2825
Fix teh misspellings
bhousel a325535
`name:pronunciation` is not namelike
bhousel f61a3ef
Don't replace `flag:name` tag - it's expected to be in local language
bhousel 96298f2
Preserve `name` value if this preset shows `brand` or `operator` field
bhousel 4f369a8
Match the prereleased nsi v5 (for now)
bhousel 11201eb
Rewrite the validator in ES6/Promises, several improvements here:
bhousel f87c2d9
Allow validators to return provisional results, revalidate after delay
bhousel 16f2f07
Merge pull request #8319 from openstreetmap/promisify_validation
bhousel 3640e15
Fix misspelling "coprorate" -> "corporate"
bhousel b032cd9
Adust NSI matching validation code:
bhousel 3665f80
Also include `operator:wikidata` as a wikidata tag
bhousel 90bbe38
Remove the brand combo from the name field
bhousel 7694335
Better handling of headGraph, separate head and base queues
bhousel 4d9336b
Checkin en.min.json
bhousel 1f6a212
Move all of NSI into a service, rewrite matcher code
bhousel 42dccbf
When displaying a preset image, use display:none for siblings
bhousel dc22678
Unsquish the issue messages by adding more side padding
bhousel bbed217
For some names, consider splitting `name` into `name` and `branch`..
bhousel 1b1bf8e
Don't offer upgrades to dissolved items
bhousel 3f8faec
Improvements to name gathering
bhousel 7a82dba
Only match alternate `amenity/yes` if it actually is tagged that way
bhousel a656106
Include nsi_dissolved in test setup
bhousel a827e13
Be less aggressive about removing toplevel tags
bhousel c3e9e8c
Support a more verbose format for listing issues
bhousel 2b7adf8
Remember user's preference for expanding issue-info section
bhousel 9f30ebf
Adjust verbose utilDisplayLabel call, use for more validation messages
bhousel 19a8fd1
Be smarter about identifying what tree an osm feature might be in
bhousel 2e9c463
update imagery
bhousel 98a622f
Make sure a name is either primary or alternate (can't be both)
bhousel d282140
If we match a generic name, stop looking
bhousel f95e7db
Create the categories like the presets
bhousel 3cf5f69
Allow missing locationSetID on presets, fields, categories
bhousel f5b6024
Revise name/branch splitting code
bhousel 77e7620
Switch to published NSI v5 :tada:
bhousel 55d9da9
Improve logic for matching name fragments like TUI ReiseCenter
bhousel ec787f8
Location-aware preset matching
bhousel 9537911
Add guard code in `locationsAt`, for testing entities with invalid loc
bhousel add1143
More sophisticated name/branch splitting
bhousel 0d79e8e
Upgraded to location-conflation v0.8.0
bhousel 3078f95
bump versions
bhousel 896d14b
Upgrade to name-suggestion-index v6
bhousel bfb36d5
If locationSet is missing `include`, default to worldwide include
bhousel 8db1c1f
Construct URL to match version number that package.json has
bhousel ba01676
Remove unnecessary context argument
bhousel d203699
Merge conflicts resolved
mbrzakovic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
import LocationConflation from '@ideditor/location-conflation'; | ||
import whichPolygon from 'which-polygon'; | ||
import calcArea from '@mapbox/geojson-area'; | ||
import { utilArrayChunk } from '../util'; | ||
|
||
let _mainLocations = coreLocations(); // singleton | ||
export { _mainLocations as locationManager }; | ||
|
||
// | ||
// `coreLocations` maintains an internal index of all the boundaries/geofences used by iD. | ||
// It's used by presets, community index, background imagery, to know where in the world these things are valid. | ||
// These geofences should be defined by `locationSet` objects: | ||
// | ||
// let locationSet = { | ||
// include: [ Array of locations ], | ||
// exclude: [ Array of locations ] | ||
// }; | ||
// | ||
// For more info see the location-conflation and country-coder projects, see: | ||
// https://github.com/ideditor/location-conflation | ||
// https://github.com/ideditor/country-coder | ||
// | ||
export function coreLocations() { | ||
let _this = {}; | ||
let _resolvedFeatures = {}; // cache of *resolved* locationSet features | ||
let _loco = new LocationConflation(); // instance of a location-conflation resolver | ||
let _wp; // instance of a which-polygon index | ||
|
||
// pre-resolve the worldwide locationSet | ||
const world = { locationSet: { include: ['Q2'] } }; | ||
resolveLocationSet(world); | ||
rebuildIndex(); | ||
|
||
let _queue = []; | ||
let _deferred = new Set(); | ||
let _inProcess; | ||
|
||
|
||
// Returns a Promise to process the queue | ||
function processQueue() { | ||
if (!_queue.length) return Promise.resolve(); | ||
|
||
// console.log(`queue length ${_queue.length}`); | ||
mbrzakovic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const chunk = _queue.pop(); | ||
return new Promise(resolvePromise => { | ||
const handle = window.requestIdleCallback(() => { | ||
_deferred.delete(handle); | ||
// const t0 = performance.now(); | ||
chunk.forEach(resolveLocationSet); | ||
// const t1 = performance.now(); | ||
// console.log('chunk processed in ' + (t1 - t0) + ' ms'); | ||
resolvePromise(); | ||
}); | ||
_deferred.add(handle); | ||
}) | ||
.then(() => processQueue()); | ||
mbrzakovic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
// Pass an Object with a `locationSet` property, | ||
// Performs the locationSet resolution, caches the result, and sets a `locationSetID` property on the object. | ||
function resolveLocationSet(obj) { | ||
if (obj.locationSetID) return; // work was done already | ||
|
||
try { | ||
const locationSet = obj.locationSet; | ||
if (!locationSet) { | ||
throw new Error('object missing locationSet property'); | ||
} | ||
const resolved = _loco.resolveLocationSet(locationSet); | ||
bhousel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const locationSetID = resolved.id; | ||
obj.locationSetID = locationSetID; | ||
|
||
if (!resolved.feature.geometry.coordinates.length || !resolved.feature.properties.area) { | ||
throw new Error(`locationSet ${locationSetID} resolves to an empty feature.`); | ||
} | ||
if (!_resolvedFeatures[locationSetID]) { // First time seeing this locationSet feature | ||
mbrzakovic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
let feature = JSON.parse(JSON.stringify(resolved.feature)); // deep clone | ||
feature.id = locationSetID; // Important: always use the locationSet `id` (`+[Q30]`), not the feature `id` (`Q30`) | ||
feature.properties.id = locationSetID; | ||
_resolvedFeatures[locationSetID] = feature; // insert into cache | ||
} | ||
} catch (err) { | ||
obj.locationSet = { include: ['Q2'] }; // default worldwide | ||
obj.locationSetID = '+[Q2]'; | ||
} | ||
} | ||
|
||
// Rebuilds the whichPolygon index with whatever features have been resolved. | ||
function rebuildIndex() { | ||
_wp = whichPolygon({ features: Object.values(_resolvedFeatures) }); | ||
} | ||
|
||
// | ||
// `mergeCustomGeoJSON` | ||
// Accepts an FeatureCollection-like object containing custom locations | ||
// Each feature must have a filename-like `id`, for example: `something.geojson` | ||
// | ||
// { | ||
// "type": "FeatureCollection" | ||
// "features": [ | ||
// { | ||
// "type": "Feature", | ||
// "id": "philly_metro.geojson", | ||
// "properties": { … }, | ||
// "geometry": { … } | ||
// } | ||
// ] | ||
// } | ||
// | ||
_this.mergeCustomGeoJSON = (fc) => { | ||
if (fc && fc.type === 'FeatureCollection' && Array.isArray(fc.features)) { | ||
fc.features.forEach(feature => { | ||
feature.properties = feature.properties || {}; | ||
let props = feature.properties; | ||
|
||
// Get `id` from either `id` or `properties` | ||
let id = feature.id || props.id; | ||
if (!id || !/^\S+\.geojson$/i.test(id)) return; | ||
|
||
// Ensure `id` exists and is lowercase | ||
id = id.toLowerCase(); | ||
feature.id = id; | ||
props.id = id; | ||
|
||
// Ensure `area` property exists | ||
if (!props.area) { | ||
const area = calcArea.geometry(feature.geometry) / 1e6; // m² to km² | ||
props.area = Number(area.toFixed(2)); | ||
} | ||
|
||
_loco._cache[id] = feature; | ||
}); | ||
} | ||
}; | ||
|
||
|
||
// | ||
// `mergeLocationSets` | ||
// Accepts an Array of Objects containing `locationSet` properties. | ||
// The locationSets will be resolved and indexed in the background. | ||
// [ | ||
// { id: 'preset1', locationSet: {…} }, | ||
// { id: 'preset2', locationSet: {…} }, | ||
// { id: 'preset3', locationSet: {…} }, | ||
// … | ||
// ] | ||
// After resolving and indexing, the Objects will be decorated with a | ||
// `locationSetID` property. | ||
// [ | ||
// { id: 'preset1', locationSet: {…}, locationSetID: '+[Q2]' }, | ||
// { id: 'preset2', locationSet: {…}, locationSetID: '+[Q30]' }, | ||
// { id: 'preset3', locationSet: {…}, locationSetID: '+[Q2]' }, | ||
// … | ||
// ] | ||
// | ||
// Returns a Promise fullfilled when the resolving/indexing has been completed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. haha spellchecker |
||
// This will take some seconds but happen in the background during browser idle time. | ||
// | ||
_this.mergeLocationSets = (objects) => { | ||
if (!Array.isArray(objects)) return Promise.reject('nothing to do'); | ||
|
||
// Resolve all locationSets -> geojson, processing data in chunks | ||
// | ||
// Because this will happen during idle callbacks, we want to choose a chunk size | ||
// that won't make the browser stutter too badly. LocationSets that are a simple | ||
// country coder include will resolve instantly, but ones that involve complex | ||
// include/exclude operations will take some milliseconds longer. | ||
// | ||
// Some discussion and performance results on these tickets: | ||
// https://github.com/ideditor/location-conflation/issues/26 | ||
// https://github.com/osmlab/name-suggestion-index/issues/4784#issuecomment-742003434 | ||
_queue = _queue.concat(utilArrayChunk(objects, 200)); | ||
|
||
if (!_inProcess) { | ||
_inProcess = processQueue() | ||
.then(() => { | ||
rebuildIndex(); | ||
_inProcess = null; | ||
return objects; | ||
}); | ||
} | ||
return _inProcess; | ||
}; | ||
|
||
|
||
// | ||
// `locationSetID` | ||
// Returns a locationSetID for a given locationSet (fallback to `+[Q2]`, world) | ||
// (The locationset doesn't necessarily need to be resolved to compute its `id`) | ||
// | ||
// Arguments | ||
// `locationSet`: A locationSet, e.g. `{ include: ['us'] }` | ||
// Returns | ||
// The locationSetID, e.g. `+[Q30]` | ||
// | ||
_this.locationSetID = (locationSet) => { | ||
let locationSetID; | ||
try { | ||
locationSetID = _loco.validateLocationSet(locationSet).id; | ||
} catch (err) { | ||
locationSetID = '+[Q2]'; // the world | ||
} | ||
return locationSetID; | ||
}; | ||
|
||
|
||
// | ||
// `feature` | ||
// Returns the resolved GeoJSON feature for a given locationSetID (fallback to 'world') | ||
// | ||
// Arguments | ||
// `locationSetID`: id of the form like `+[Q30]` (United States) | ||
// Returns | ||
// A GeoJSON feature: | ||
// { | ||
// type: 'Feature', | ||
// id: '+[Q30]', | ||
// properties: { id: '+[Q30]', area: 21817019.17, … }, | ||
// geometry: { … } | ||
// } | ||
_this.feature = (locationSetID) => _resolvedFeatures[locationSetID] || _resolvedFeatures['+[Q2]']; | ||
|
||
|
||
// | ||
// `locationsAt` | ||
// Find all the resolved locationSets valid at the given location. | ||
// Results include the area (in km²) to facilitate sorting. | ||
// | ||
// Arguments | ||
// `loc`: the [lon,lat] location to query, e.g. `[-74.4813, 40.7967]` | ||
// Returns | ||
// Object of locationSetIDs to areas (in km²) | ||
// { | ||
// "+[Q2]": 511207893.3958111, | ||
// "+[Q30]": 21817019.17, | ||
// "+[new_jersey.geojson]": 22390.77, | ||
// … | ||
// } | ||
// | ||
_this.locationsAt = (loc) => { | ||
let result = {}; | ||
_wp(loc, true).forEach(prop => result[prop.id] = prop.area); | ||
return result; | ||
}; | ||
|
||
// | ||
// `query` | ||
// Execute a query directly against which-polygon | ||
// https://github.com/mapbox/which-polygon | ||
// | ||
// Arguments | ||
// `loc`: the [lon,lat] location to query, | ||
// `multi`: `true` to return all results, `false` to return first result | ||
// Returns | ||
// Array of GeoJSON *properties* for the locationSet features that exist at `loc` | ||
// | ||
_this.query = (loc, multi) => _wp(loc, multi); | ||
|
||
// Direct access to the location-conflation resolver | ||
_this.loco = () => _loco; | ||
|
||
// Direct access to the which-polygon index | ||
_this.wp = () => _wp; | ||
|
||
|
||
return _this; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I should add - I don't actually want to load these from the GitHub
main
branch..These URLs just for testing it.