From 645cc790a3a488db27dd56268e17c9625833cc8e Mon Sep 17 00:00:00 2001 From: Bryan Housel Date: Thu, 12 Jul 2018 23:43:37 -0400 Subject: [PATCH] Add docs about OSM API, finish implementing `postNoteUpdate` `postNoteUpdate` can hangle status changes and comment additions (I named it that to be like `putChangeset`) Also renamed `user` to `loadUser` to be consistent with other calls --- modules/services/osm.js | 232 +++++++++++++++++++++--------------- modules/ui/note_comments.js | 2 +- 2 files changed, 139 insertions(+), 95 deletions(-) diff --git a/modules/services/osm.js b/modules/services/osm.js index 583980f18..6311c6e9b 100644 --- a/modules/services/osm.js +++ b/modules/services/osm.js @@ -28,7 +28,11 @@ import { osmWay } from '../osm'; -import { utilRebind, utilIdleWorker } from '../util'; +import { + utilRebind, + utilIdleWorker, + utilQsString +} from '../util'; var dispatch = d3_dispatch('authLoading', 'authDone', 'change', 'loading', 'loaded', 'loadedNotes'); @@ -43,10 +47,9 @@ var oauth = osmAuth({ var _blacklists = ['.*\.google(apis)?\..*/(vt|kh)[\?/].*([xyz]=.*){3}.*']; var _tileCache = { loaded: {}, inflight: {}, seen: {} }; -var _noteCache = { loaded: {}, inflight: {}, note: {}, rtree: rbush() }; +var _noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, rtree: rbush() }; var _userCache = { toLoad: {}, user: {} }; var _changeset = {}; -var _noteChangeset = {}; var _connectionID = 1; var _tileZoom = 16; @@ -326,14 +329,13 @@ export default { _forEach(_tileCache.inflight, abortRequest); _forEach(_noteCache.inflight, abortRequest); + _forEach(_noteCache.inflightPost, abortRequest); if (_changeset.inflight) abortRequest(_changeset.inflight); - if (_noteChangeset.inflight) abortRequest(_changeset.inflight); _tileCache = { loaded: {}, inflight: {}, seen: {} }; - _noteCache = { loaded: {}, inflight: {}, note: {}, rtree: rbush() }; + _noteCache = { loaded: {}, inflight: {}, inflightPost: {}, note: {}, rtree: rbush() }; _userCache = { toLoad: {}, user: {} }; _changeset = {}; - _noteChangeset = {}; return this; }, @@ -373,6 +375,8 @@ export default { }, + // Generic method to load data from the OSM API + // Can handle either auth or unauth calls. loadFromAPI: function(path, callback, options) { options = _extend({ skipSeen: true }, options); var that = this; @@ -421,6 +425,9 @@ export default { }, + // Load a single entity by id (ways and relations use the `/full` call) + // GET /api/0.6/node/#id + // GET /api/0.6/[way|relation]/#id/full loadEntity: function(id, callback) { var type = osmEntity.id.type(id); var osmID = osmEntity.id.toOSM(id); @@ -436,6 +443,8 @@ export default { }, + // Load a single entity with a specific version + // GET /api/0.6/[node|way|relation]/#id/#version loadEntityVersion: function(id, version, callback) { var type = osmEntity.id.type(id); var osmID = osmEntity.id.toOSM(id); @@ -451,8 +460,9 @@ export default { }, - // load multiple entities - // callback may be called multiple times + // Load multiple entities in chunks + // (note: callback may be called multiple times) + // GET /api/0.6/[nodes|ways|relations]?#parameters loadMultiple: function(ids, callback) { var that = this; @@ -474,6 +484,10 @@ export default { }, + // Create, upload, and close a changeset + // PUT /api/0.6/changeset/create + // POST /api/0.6/changeset/#id/upload + // PUT /api/0.6/changeset/#id/close putChangeset: function(changeset, changes, callback) { if (_changeset.inflight) { return callback({ message: 'Changeset already inflight', status: -2 }, changeset); @@ -548,8 +562,9 @@ export default { }, - // load multiple users - // callback may be called multiple times + // Load multiple users in chunks + // (note: callback may be called multiple times) + // GET /api/0.6/users?users=#id1,#id2,...,#idn loadUsers: function(uids, callback) { var toLoad = []; var cached = []; @@ -599,7 +614,9 @@ export default { }, - user: function(uid, callback) { + // Load a given user by id + // GET /api/0.6/user/#id + loadUser: function(uid, callback) { if (_userCache.user[uid] || !this.authenticated()) { // require auth delete _userCache.toLoad[uid]; callback(undefined, _userCache.user[uid]); @@ -635,6 +652,8 @@ export default { }, + // Load the details of the logged-in user + // GET /api/0.6/user/details userDetails: function(callback) { if (_userDetails) { callback(undefined, _userDetails); @@ -671,6 +690,8 @@ export default { }, + // Load previous changesets for the logged in user + // GET /api/0.6/changesets?user=#id userChangesets: function(callback) { if (_userChangesets) { callback(undefined, _userChangesets); @@ -718,6 +739,8 @@ export default { }, + // Fetch the status of the OSM API + // GET /api/capabilities status: function(callback) { var that = this; var cid = _connectionID; @@ -757,30 +780,21 @@ export default { }, - imageryBlacklists: function() { - return _blacklists; - }, - - - tileZoom: function(_) { - if (!arguments.length) return _tileZoom; - _tileZoom = _; - return this; - }, - - + // Load data (entities or notes) from the API in tiles + // GET /api/0.6/map?bbox= + // GET /api/0.6/notes?bbox= loadTiles: function(projection, dimensions, callback, noteOptions) { if (_off) return; var that = this; - var loadingNotes = (noteOptions !== undefined); - // check if loading entities, or notes + // are we loading entities or notes? + var loadingNotes = (noteOptions !== undefined); var path, cache, tilezoom, throttleLoadUsers; + if (loadingNotes) { noteOptions = _extend({ limit: 10000, closed: 7}, noteOptions); - path = '/api/0.6/notes?limit=' + noteOptions.limit + - '&closed=' + noteOptions.closed + '&bbox='; + path = '/api/0.6/notes?limit=' + noteOptions.limit + '&closed=' + noteOptions.closed + '&bbox='; cache = _noteCache; tilezoom = _noteZoom; throttleLoadUsers = _throttle(function() { @@ -788,7 +802,6 @@ export default { if (!uids.length) return; that.loadUsers(uids, function() {}); // eagerly load user details }, 750); - } else { path = '/api/0.6/map?bbox='; cache = _tileCache; @@ -872,6 +885,85 @@ export default { }, + // Load notes from the API (just calls this.loadTiles) + // GET /api/0.6/notes?bbox= + loadNotes: function(projection, dimensions, noteOptions) { + noteOptions = _extend({ limit: 10000, closed: 7}, noteOptions); + this.loadTiles(projection, dimensions, null, noteOptions); + }, + + + // Create a note + // POST /api/0.6/notes?params + postNoteCreate: function(note, callback) { + // todo + }, + + + // Update a note + // POST /api/0.6/notes/#id/comment?text=comment + // POST /api/0.6/notes/#id/close?text=comment + // POST /api/0.6/notes/#id/reopen?text=comment + postNoteUpdate: function(note, newStatus, callback) { + if (!this.authenticated()) { + return callback({ message: 'Not Authenticated', status: -3 }, note); + } + if (_noteCache.inflightPost[note.id]) { + return callback({ message: 'Note update already inflight', status: -2 }, note); + } + + var that = this; + var cid = _connectionID; + + var action; + if (note.status !== 'closed' && newStatus === 'closed') { + action = 'close'; + } else if (note.status !== 'open' && newStatus === 'open') { + action = 'reopen'; + } else { + action = 'comment'; + } + + var path = '/api/0.6/notes/' + note.id + '/' + action; + if (note.newComment) { + path += '?' + utilQsString({ text: note.newComment }); + } + + _noteCache.inflightPost[note.id] = oauth.xhr({ method: 'POST', path: path }, done); + + + function done(err, xml) { + if (err) { + // 400 Bad Request, 401 Unauthorized, 403 Forbidden.. + if (err.status === 400 || err.status === 401 || err.status === 403) { + that.logout(); + } + return callback(err); + } + if (that.getConnectionId() !== cid) { + return callback({ message: 'Connection Switched', status: -1 }); + } + + delete _noteCache.inflightPost[note.id]; + + if (xml) { // we get the updated note back, remove from caches and reparse.. + var item = { minX: note.loc[0], minY: note.loc[1], maxX: note.loc[0], maxY: note.loc[1], data: note }; + _noteCache.rtree.remove(item, function isEql(a, b) { return a.data.id === b.data.id; }); + delete _noteCache.note[note.id]; + + var options = { skipSeen: false }; + return parseXML(xml, function(err, results) { + if (err) { + return callback(err); + } else { + return callback(undefined, results[0]); + } + }, options); + } + } + }, + + switch: function(options) { urlroot = options.urlroot; @@ -894,6 +986,9 @@ export default { }, + // get/set cached data + // This is used to save/restore the state when entering/exiting the walkthrough + // Also used for testing purposes. caches: function(obj) { if (!arguments.length) { return { @@ -909,7 +1004,8 @@ export default { } if (obj.note) { _noteCache = obj.note; - _tileCache.inflight = {}; + _noteCache.inflight = {}; + _noteCache.inflightPost = {}; } if (obj.user) { _userCache = obj.user; @@ -958,12 +1054,19 @@ export default { }, - loadNotes: function(projection, dimensions, noteOptions) { - noteOptions = _extend({ limit: 10000, closed: 7}, noteOptions); - this.loadTiles(projection, dimensions, null, noteOptions); + imageryBlacklists: function() { + return _blacklists; }, + tileZoom: function(_) { + if (!arguments.length) return _tileZoom; + _tileZoom = _; + return this; + }, + + + // get all cached notes covering the viewport notes: function(projection) { var viewport = projection.clipExtent(); var min = [viewport[0][0], viewport[1][1]]; @@ -975,77 +1078,18 @@ export default { }, + // get a single note from the cache getNote: function(id) { return _noteCache.note[id]; }, + // replace a single note in the cache replaceNote: function(n) { if (n instanceof osmNote) { _noteCache.note[n.id] = n; } return n; - }, - - - toggleNoteStatus: function(note, comment, callback) { - if (!(note instanceof osmNote) && !(this.getNote(note.id))) return; - if (!this.authenticated()) return; - - var that = this; - var cid = _connectionID; - - function done(err, xml) { - if (err) { - // 400 Bad Request, 401 Unauthorized, 403 Forbidden.. - if (err.status === 400 || err.status === 401 || err.status === 403) { - that.logout(); - } - return callback(err); - } - if (that.getConnectionId() !== cid) { - return callback({ message: 'Connection Switched', status: -1 }); - } - - return callback(xml); - } - - var status = note.status === 'open' ? 'close' : 'reopen'; - - var path = '/api/0.6/notes/' + note.id + '/' + status; - path += comment ? '?text=' + comment : ''; - - _noteChangeset.inflight = oauth.xhr({ method: 'POST', path: path }, done); - - }, - - addNoteComment: function(note, comment, callback) { - if (!(note instanceof osmNote) && !(this.getNote(note.id))) return; - if (!this.authenticated()) return; - if (!comment) return; - - var that = this; - var cid = _connectionID; - - function done(err, xml) { - if (err) { - // 400 Bad Request, 401 Unauthorized, 403 Forbidden.. - if (err.status === 400 || err.status === 401 || err.status === 403) { - that.logout(); - } - return callback(err); - } - if (that.getConnectionId() !== cid) { - return callback({ message: 'Connection Switched', status: -1 }); - } - - return callback(xml); - } - - var path = '/api/0.6/notes/' + note.id + '/comment?text=' + comment; - - _noteChangeset.inflight = oauth.xhr({ method: 'POST', path: path }, done); - - }, + } }; diff --git a/modules/ui/note_comments.js b/modules/ui/note_comments.js index 4154f8941..a9890779c 100644 --- a/modules/ui/note_comments.js +++ b/modules/ui/note_comments.js @@ -81,7 +81,7 @@ export function uiNoteComments() { }); Object.keys(uids).forEach(function(uid) { - osm.user(uid, function(err, user) { + osm.loadUser(uid, function(err, user) { if (!user || !user.image_url) return; selection.selectAll('.comment-avatar.user-' + uid)