diff --git a/Makefile b/Makefile
index d25897d4d..d95dd85a0 100644
--- a/Makefile
+++ b/Makefile
@@ -45,7 +45,6 @@ $(BUILDJS_TARGETS): $(BUILDJS_SOURCES) build.js
MODULE_TARGETS = \
js/lib/id/index.js \
js/lib/id/behavior.js \
- js/lib/id/core.js \
js/lib/id/geo.js \
js/lib/id/modes.js \
js/lib/id/operations.js \
diff --git a/js/lib/id/index.js b/js/lib/id/index.js
index 219b9487d..c8811b6e9 100644
--- a/js/lib/id/index.js
+++ b/js/lib/id/index.js
@@ -267,6 +267,13 @@
p1[1] + (p2[1] - p1[1]) * t];
}
+ // 2D cross product of OA and OB vectors, i.e. z-component of their 3D cross product.
+ // Returns a positive value, if OAB makes a counter-clockwise turn,
+ // negative for clockwise turn, and zero if the points are collinear.
+ function cross(o, a, b) {
+ return (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
+ }
+
// http://jsperf.com/id-dist-optimization
function euclideanDistance(a, b) {
var x = a[0] - b[0], y = a[1] - b[1];
@@ -443,6 +450,27 @@
});
}
+ function polygonIntersectsPolygon(outer, inner, checkSegments) {
+ function testSegments(outer, inner) {
+ for (var i = 0; i < outer.length - 1; i++) {
+ for (var j = 0; j < inner.length - 1; j++) {
+ var a = [ outer[i], outer[i+1] ],
+ b = [ inner[j], inner[j+1] ];
+ if (lineIntersection(a, b)) return true;
+ }
+ }
+ return false;
+ }
+
+ function testPoints(outer, inner) {
+ return _.some(inner, function(point) {
+ return pointInPolygon(point, outer);
+ });
+ }
+
+ return testPoints(outer, inner) || (!!checkSegments && testSegments(outer, inner));
+ }
+
function pathLength(path) {
var length = 0,
dx, dy;
@@ -582,6 +610,17 @@
}
};
+ var pavedTags = {
+ 'surface': {
+ 'paved': true,
+ 'asphalt': true,
+ 'concrete': true
+ },
+ 'tracktype': {
+ 'grade1': true
+ }
+ };
+
function Entity(attrs) {
// For prototypal inheritance.
if (this instanceof Entity) return;
@@ -763,7 +802,7 @@
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
- var extent = iD.geo.Extent();
+ var extent = Extent();
for (var i = 0; i < this.nodes.length; i++) {
var node = resolver.hasEntity(this.nodes[i]);
if (node) {
@@ -842,7 +881,7 @@
var o = coords[(i+1) % coords.length],
a = coords[i],
b = coords[(i+2) % coords.length],
- res = iD.geo.cross(o, a, b);
+ res = cross(o, a, b);
curr = (res > 0) ? 1 : (res < 0) ? -1 : 0;
if (curr === 0) {
@@ -1031,11 +1070,11 @@
extent: function(resolver, memo) {
return resolver.transient(this, 'extent', function() {
- if (memo && memo[this.id]) return iD.geo.Extent();
+ if (memo && memo[this.id]) return Extent();
memo = memo || {};
memo[this.id] = true;
- var extent = iD.geo.Extent();
+ var extent = Extent();
for (var i = 0; i < this.members.length; i++) {
var member = resolver.hasEntity(this.members[i].id);
if (member) {
@@ -1214,8 +1253,8 @@
var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); }),
inners = this.members.filter(function(m) { return 'inner' === m.role; });
- outers = iD.geo.joinWays(outers, resolver);
- inners = iD.geo.joinWays(inners, resolver);
+ outers = joinWays(outers, resolver);
+ inners = joinWays(inners, resolver);
outers = outers.map(function(outer) { return _.map(outer.nodes, 'loc'); });
inners = inners.map(function(inner) { return _.map(inner.nodes, 'loc'); });
@@ -1231,13 +1270,13 @@
for (o = 0; o < outers.length; o++) {
outer = outers[o];
- if (iD.geo.polygonContainsPolygon(outer, inner))
+ if (polygonContainsPolygon(outer, inner))
return o;
}
for (o = 0; o < outers.length; o++) {
outer = outers[o];
- if (iD.geo.polygonIntersectsPolygon(outer, inner))
+ if (polygonIntersectsPolygon(outer, inner))
return o;
}
}
@@ -1276,7 +1315,7 @@
type: 'node',
extent: function() {
- return new iD.geo.Extent(this.loc);
+ return new Extent(this.loc);
},
geometry: function(graph) {
@@ -1333,6 +1372,1595 @@
}
});
+ function Connection(useHttps) {
+ if (typeof useHttps !== 'boolean') {
+ useHttps = window.location.protocol === 'https:';
+ }
+
+ var event = d3.dispatch('authenticating', 'authenticated', 'auth', 'loading', 'loaded'),
+ protocol = useHttps ? 'https:' : 'http:',
+ url = protocol + '//www.openstreetmap.org',
+ connection = {},
+ inflight = {},
+ loadedTiles = {},
+ tileZoom = 16,
+ oauth = osmAuth({
+ url: protocol + '//www.openstreetmap.org',
+ oauth_consumer_key: '5A043yRSEugj4DJ5TljuapfnrflWDte8jTOcWLlT',
+ oauth_secret: 'aB3jKq1TRsCOUrfOIZ6oQMEDmv2ptV76PA54NGLL',
+ loading: authenticating,
+ done: authenticated
+ }),
+ ndStr = 'nd',
+ tagStr = 'tag',
+ memberStr = 'member',
+ nodeStr = 'node',
+ wayStr = 'way',
+ relationStr = 'relation',
+ userDetails,
+ off;
+
+
+ connection.changesetURL = function(changesetId) {
+ return url + '/changeset/' + changesetId;
+ };
+
+ connection.changesetsURL = function(center, zoom) {
+ var precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
+ return url + '/history#map=' +
+ Math.floor(zoom) + '/' +
+ center[1].toFixed(precision) + '/' +
+ center[0].toFixed(precision);
+ };
+
+ connection.entityURL = function(entity) {
+ return url + '/' + entity.type + '/' + entity.osmId();
+ };
+
+ connection.userURL = function(username) {
+ return url + '/user/' + username;
+ };
+
+ connection.loadFromURL = function(url, callback) {
+ function done(err, dom) {
+ return callback(err, parse(dom));
+ }
+ return d3.xml(url).get(done);
+ };
+
+ connection.loadEntity = function(id, callback) {
+ var type = Entity.id.type(id),
+ osmID = Entity.id.toOSM(id);
+
+ connection.loadFromURL(
+ url + '/api/0.6/' + type + '/' + osmID + (type !== 'node' ? '/full' : ''),
+ function(err, entities) {
+ if (callback) callback(err, {data: entities});
+ });
+ };
+
+ connection.loadEntityVersion = function(id, version, callback) {
+ var type = Entity.id.type(id),
+ osmID = Entity.id.toOSM(id);
+
+ connection.loadFromURL(
+ url + '/api/0.6/' + type + '/' + osmID + '/' + version,
+ function(err, entities) {
+ if (callback) callback(err, {data: entities});
+ });
+ };
+
+ connection.loadMultiple = function(ids, callback) {
+ _.each(_.groupBy(_.uniq(ids), Entity.id.type), function(v, k) {
+ var type = k + 's',
+ osmIDs = _.map(v, Entity.id.toOSM);
+
+ _.each(_.chunk(osmIDs, 150), function(arr) {
+ connection.loadFromURL(
+ url + '/api/0.6/' + type + '?' + type + '=' + arr.join(),
+ function(err, entities) {
+ if (callback) callback(err, {data: entities});
+ });
+ });
+ });
+ };
+
+ function authenticating() {
+ event.authenticating();
+ }
+
+ function authenticated() {
+ event.authenticated();
+ }
+
+ function getLoc(attrs) {
+ var lon = attrs.lon && attrs.lon.value,
+ lat = attrs.lat && attrs.lat.value;
+ return [parseFloat(lon), parseFloat(lat)];
+ }
+
+ function getNodes(obj) {
+ var elems = obj.getElementsByTagName(ndStr),
+ nodes = new Array(elems.length);
+ for (var i = 0, l = elems.length; i < l; i++) {
+ nodes[i] = 'n' + elems[i].attributes.ref.value;
+ }
+ return nodes;
+ }
+
+ function getTags(obj) {
+ var elems = obj.getElementsByTagName(tagStr),
+ tags = {};
+ for (var i = 0, l = elems.length; i < l; i++) {
+ var attrs = elems[i].attributes;
+ tags[attrs.k.value] = attrs.v.value;
+ }
+ return tags;
+ }
+
+ function getMembers(obj) {
+ var elems = obj.getElementsByTagName(memberStr),
+ members = new Array(elems.length);
+ for (var i = 0, l = elems.length; i < l; i++) {
+ var attrs = elems[i].attributes;
+ members[i] = {
+ id: attrs.type.value[0] + attrs.ref.value,
+ type: attrs.type.value,
+ role: attrs.role.value
+ };
+ }
+ return members;
+ }
+
+ function getVisible(attrs) {
+ return (!attrs.visible || attrs.visible.value !== 'false');
+ }
+
+ var parsers = {
+ node: function nodeData(obj) {
+ var attrs = obj.attributes;
+ return new Node({
+ id: Entity.id.fromOSM(nodeStr, attrs.id.value),
+ loc: getLoc(attrs),
+ version: attrs.version.value,
+ user: attrs.user && attrs.user.value,
+ tags: getTags(obj),
+ visible: getVisible(attrs)
+ });
+ },
+
+ way: function wayData(obj) {
+ var attrs = obj.attributes;
+ return new Way({
+ id: Entity.id.fromOSM(wayStr, attrs.id.value),
+ version: attrs.version.value,
+ user: attrs.user && attrs.user.value,
+ tags: getTags(obj),
+ nodes: getNodes(obj),
+ visible: getVisible(attrs)
+ });
+ },
+
+ relation: function relationData(obj) {
+ var attrs = obj.attributes;
+ return new Relation({
+ id: Entity.id.fromOSM(relationStr, attrs.id.value),
+ version: attrs.version.value,
+ user: attrs.user && attrs.user.value,
+ tags: getTags(obj),
+ members: getMembers(obj),
+ visible: getVisible(attrs)
+ });
+ }
+ };
+
+ function parse(dom) {
+ if (!dom || !dom.childNodes) return;
+
+ var root = dom.childNodes[0],
+ children = root.childNodes,
+ entities = [];
+
+ for (var i = 0, l = children.length; i < l; i++) {
+ var child = children[i],
+ parser = parsers[child.nodeName];
+ if (parser) {
+ entities.push(parser(child));
+ }
+ }
+
+ return entities;
+ }
+
+ connection.authenticated = function() {
+ return oauth.authenticated();
+ };
+
+ // Generate Changeset XML. Returns a string.
+ connection.changesetJXON = function(tags) {
+ return {
+ osm: {
+ changeset: {
+ tag: _.map(tags, function(value, key) {
+ return { '@k': key, '@v': value };
+ }),
+ '@version': 0.6,
+ '@generator': 'iD'
+ }
+ }
+ };
+ };
+
+ // Generate [osmChange](http://wiki.openstreetmap.org/wiki/OsmChange)
+ // XML. Returns a string.
+ connection.osmChangeJXON = function(changeset_id, changes) {
+ function nest(x, order) {
+ var groups = {};
+ for (var i = 0; i < x.length; i++) {
+ var tagName = Object.keys(x[i])[0];
+ if (!groups[tagName]) groups[tagName] = [];
+ groups[tagName].push(x[i][tagName]);
+ }
+ var ordered = {};
+ order.forEach(function(o) {
+ if (groups[o]) ordered[o] = groups[o];
+ });
+ return ordered;
+ }
+
+ function rep(entity) {
+ return entity.asJXON(changeset_id);
+ }
+
+ return {
+ osmChange: {
+ '@version': 0.6,
+ '@generator': 'iD',
+ 'create': nest(changes.created.map(rep), ['node', 'way', 'relation']),
+ 'modify': nest(changes.modified.map(rep), ['node', 'way', 'relation']),
+ 'delete': _.extend(nest(changes.deleted.map(rep), ['relation', 'way', 'node']), {'@if-unused': true})
+ }
+ };
+ };
+
+ connection.changesetTags = function(comment, imageryUsed) {
+ var detected = iD.detect(),
+ tags = {
+ created_by: 'iD ' + iD.version,
+ imagery_used: imageryUsed.join(';').substr(0, 255),
+ host: (window.location.origin + window.location.pathname).substr(0, 255),
+ locale: detected.locale
+ };
+
+ if (comment) {
+ tags.comment = comment.substr(0, 255);
+ }
+
+ return tags;
+ };
+
+ connection.putChangeset = function(changes, comment, imageryUsed, callback) {
+ oauth.xhr({
+ method: 'PUT',
+ path: '/api/0.6/changeset/create',
+ options: { header: { 'Content-Type': 'text/xml' } },
+ content: JXON.stringify(connection.changesetJXON(connection.changesetTags(comment, imageryUsed)))
+ }, function(err, changeset_id) {
+ if (err) return callback(err);
+ oauth.xhr({
+ method: 'POST',
+ path: '/api/0.6/changeset/' + changeset_id + '/upload',
+ options: { header: { 'Content-Type': 'text/xml' } },
+ content: JXON.stringify(connection.osmChangeJXON(changeset_id, changes))
+ }, function(err) {
+ if (err) return callback(err);
+ // POST was successful, safe to call the callback.
+ // Still attempt to close changeset, but ignore response because #2667
+ // Add delay to allow for postgres replication #1646 #2678
+ window.setTimeout(function() { callback(null, changeset_id); }, 2500);
+ oauth.xhr({
+ method: 'PUT',
+ path: '/api/0.6/changeset/' + changeset_id + '/close',
+ options: { header: { 'Content-Type': 'text/xml' } }
+ }, d3.functor(true));
+ });
+ });
+ };
+
+ connection.userDetails = function(callback) {
+ if (userDetails) {
+ callback(undefined, userDetails);
+ return;
+ }
+
+ function done(err, user_details) {
+ if (err) return callback(err);
+
+ var u = user_details.getElementsByTagName('user')[0],
+ img = u.getElementsByTagName('img'),
+ image_url = '';
+
+ if (img && img[0] && img[0].getAttribute('href')) {
+ image_url = img[0].getAttribute('href');
+ }
+
+ userDetails = {
+ display_name: u.attributes.display_name.value,
+ image_url: image_url,
+ id: u.attributes.id.value
+ };
+
+ callback(undefined, userDetails);
+ }
+
+ oauth.xhr({ method: 'GET', path: '/api/0.6/user/details' }, done);
+ };
+
+ connection.userChangesets = function(callback) {
+ connection.userDetails(function(err, user) {
+ if (err) return callback(err);
+
+ function done(changesets) {
+ callback(undefined, Array.prototype.map.call(changesets.getElementsByTagName('changeset'),
+ function (changeset) {
+ return { tags: getTags(changeset) };
+ }));
+ }
+
+ d3.xml(url + '/api/0.6/changesets?user=' + user.id).get()
+ .on('load', done)
+ .on('error', callback);
+ });
+ };
+
+ connection.status = function(callback) {
+ function done(capabilities) {
+ var apiStatus = capabilities.getElementsByTagName('status');
+ callback(undefined, apiStatus[0].getAttribute('api'));
+ }
+ d3.xml(url + '/api/capabilities').get()
+ .on('load', done)
+ .on('error', callback);
+ };
+
+ function abortRequest(i) { i.abort(); }
+
+ connection.tileZoom = function(_) {
+ if (!arguments.length) return tileZoom;
+ tileZoom = _;
+ return connection;
+ };
+
+ connection.loadTiles = function(projection, dimensions, callback) {
+
+ if (off) return;
+
+ var s = projection.scale() * 2 * Math.PI,
+ z = Math.max(Math.log(s) / Math.log(2) - 8, 0),
+ ts = 256 * Math.pow(2, z - tileZoom),
+ origin = [
+ s / 2 - projection.translate()[0],
+ s / 2 - projection.translate()[1]];
+
+ var tiles = d3.geo.tile()
+ .scaleExtent([tileZoom, tileZoom])
+ .scale(s)
+ .size(dimensions)
+ .translate(projection.translate())()
+ .map(function(tile) {
+ var x = tile[0] * ts - origin[0],
+ y = tile[1] * ts - origin[1];
+
+ return {
+ id: tile.toString(),
+ extent: Extent(
+ projection.invert([x, y + ts]),
+ projection.invert([x + ts, y]))
+ };
+ });
+
+ function bboxUrl(tile) {
+ return url + '/api/0.6/map?bbox=' + tile.extent.toParam();
+ }
+
+ _.filter(inflight, function(v, i) {
+ var wanted = _.find(tiles, function(tile) {
+ return i === tile.id;
+ });
+ if (!wanted) delete inflight[i];
+ return !wanted;
+ }).map(abortRequest);
+
+ tiles.forEach(function(tile) {
+ var id = tile.id;
+
+ if (loadedTiles[id] || inflight[id]) return;
+
+ if (_.isEmpty(inflight)) {
+ event.loading();
+ }
+
+ inflight[id] = connection.loadFromURL(bboxUrl(tile), function(err, parsed) {
+ loadedTiles[id] = true;
+ delete inflight[id];
+
+ if (callback) callback(err, _.extend({data: parsed}, tile));
+
+ if (_.isEmpty(inflight)) {
+ event.loaded();
+ }
+ });
+ });
+ };
+
+ connection.switch = function(options) {
+ url = options.url;
+ oauth.options(_.extend({
+ loading: authenticating,
+ done: authenticated
+ }, options));
+ event.auth();
+ connection.flush();
+ return connection;
+ };
+
+ connection.toggle = function(_) {
+ off = !_;
+ return connection;
+ };
+
+ connection.flush = function() {
+ userDetails = undefined;
+ _.forEach(inflight, abortRequest);
+ loadedTiles = {};
+ inflight = {};
+ return connection;
+ };
+
+ connection.loadedTiles = function(_) {
+ if (!arguments.length) return loadedTiles;
+ loadedTiles = _;
+ return connection;
+ };
+
+ connection.logout = function() {
+ userDetails = undefined;
+ oauth.logout();
+ event.auth();
+ return connection;
+ };
+
+ connection.authenticate = function(callback) {
+ userDetails = undefined;
+ function done(err, res) {
+ event.auth();
+ if (callback) callback(err, res);
+ }
+ return oauth.authenticate(done);
+ };
+
+ return d3.rebind(connection, event, 'on');
+ }
+
+ /*
+ iD.Difference represents the difference between two graphs.
+ It knows how to calculate the set of entities that were
+ created, modified, or deleted, and also contains the logic
+ for recursively extending a difference to the complete set
+ of entities that will require a redraw, taking into account
+ child and parent relationships.
+ */
+ function Difference(base, head) {
+ var changes = {}, length = 0;
+
+ function changed(h, b) {
+ return h !== b && !_.isEqual(_.omit(h, 'v'), _.omit(b, 'v'));
+ }
+
+ _.each(head.entities, function(h, id) {
+ var b = base.entities[id];
+ if (changed(h, b)) {
+ changes[id] = {base: b, head: h};
+ length++;
+ }
+ });
+
+ _.each(base.entities, function(b, id) {
+ var h = head.entities[id];
+ if (!changes[id] && changed(h, b)) {
+ changes[id] = {base: b, head: h};
+ length++;
+ }
+ });
+
+ function addParents(parents, result) {
+ for (var i = 0; i < parents.length; i++) {
+ var parent = parents[i];
+
+ if (parent.id in result)
+ continue;
+
+ result[parent.id] = parent;
+ addParents(head.parentRelations(parent), result);
+ }
+ }
+
+ var difference = {};
+
+ difference.length = function() {
+ return length;
+ };
+
+ difference.changes = function() {
+ return changes;
+ };
+
+ difference.extantIDs = function() {
+ var result = [];
+ _.each(changes, function(change, id) {
+ if (change.head) result.push(id);
+ });
+ return result;
+ };
+
+ difference.modified = function() {
+ var result = [];
+ _.each(changes, function(change) {
+ if (change.base && change.head) result.push(change.head);
+ });
+ return result;
+ };
+
+ difference.created = function() {
+ var result = [];
+ _.each(changes, function(change) {
+ if (!change.base && change.head) result.push(change.head);
+ });
+ return result;
+ };
+
+ difference.deleted = function() {
+ var result = [];
+ _.each(changes, function(change) {
+ if (change.base && !change.head) result.push(change.base);
+ });
+ return result;
+ };
+
+ difference.summary = function() {
+ var relevant = {};
+
+ function addEntity(entity, graph, changeType) {
+ relevant[entity.id] = {
+ entity: entity,
+ graph: graph,
+ changeType: changeType
+ };
+ }
+
+ function addParents(entity) {
+ var parents = head.parentWays(entity);
+ for (var j = parents.length - 1; j >= 0; j--) {
+ var parent = parents[j];
+ if (!(parent.id in relevant)) addEntity(parent, head, 'modified');
+ }
+ }
+
+ _.each(changes, function(change) {
+ if (change.head && change.head.geometry(head) !== 'vertex') {
+ addEntity(change.head, head, change.base ? 'modified' : 'created');
+
+ } else if (change.base && change.base.geometry(base) !== 'vertex') {
+ addEntity(change.base, base, 'deleted');
+
+ } else if (change.base && change.head) { // modified vertex
+ var moved = !_.isEqual(change.base.loc, change.head.loc),
+ retagged = !_.isEqual(change.base.tags, change.head.tags);
+
+ if (moved) {
+ addParents(change.head);
+ }
+
+ if (retagged || (moved && change.head.hasInterestingTags())) {
+ addEntity(change.head, head, 'modified');
+ }
+
+ } else if (change.head && change.head.hasInterestingTags()) { // created vertex
+ addEntity(change.head, head, 'created');
+
+ } else if (change.base && change.base.hasInterestingTags()) { // deleted vertex
+ addEntity(change.base, base, 'deleted');
+ }
+ });
+
+ return d3.values(relevant);
+ };
+
+ difference.complete = function(extent) {
+ var result = {}, id, change;
+
+ for (id in changes) {
+ change = changes[id];
+
+ var h = change.head,
+ b = change.base,
+ entity = h || b;
+
+ if (extent &&
+ (!h || !h.intersects(extent, head)) &&
+ (!b || !b.intersects(extent, base)))
+ continue;
+
+ result[id] = h;
+
+ if (entity.type === 'way') {
+ var nh = h ? h.nodes : [],
+ nb = b ? b.nodes : [],
+ diff, i;
+
+ diff = _.difference(nh, nb);
+ for (i = 0; i < diff.length; i++) {
+ result[diff[i]] = head.hasEntity(diff[i]);
+ }
+
+ diff = _.difference(nb, nh);
+ for (i = 0; i < diff.length; i++) {
+ result[diff[i]] = head.hasEntity(diff[i]);
+ }
+ }
+
+ addParents(head.parentWays(entity), result);
+ addParents(head.parentRelations(entity), result);
+ }
+
+ return result;
+ };
+
+ return difference;
+ }
+
+ /* eslint-disable no-proto */
+ var getPrototypeOf = Object.getPrototypeOf || function(obj) { return obj.__proto__; };
+ // wraps an index to an interval [0..length-1]
+ function Wrap(index, length) {
+ if (index < 0)
+ index += Math.ceil(-index/length)*length;
+ return index % length;
+ }
+
+ // A per-domain session mutex backed by a cookie and dead man's
+ // switch. If the session crashes, the mutex will auto-release
+ // after 5 seconds.
+
+ function SessionMutex(name) {
+ var mutex = {},
+ intervalID;
+
+ function renew() {
+ var expires = new Date();
+ expires.setSeconds(expires.getSeconds() + 5);
+ document.cookie = name + '=1; expires=' + expires.toUTCString();
+ }
+
+ mutex.lock = function() {
+ if (intervalID) return true;
+ var cookie = document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + name + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1');
+ if (cookie) return false;
+ renew();
+ intervalID = window.setInterval(renew, 4000);
+ return true;
+ };
+
+ mutex.unlock = function() {
+ if (!intervalID) return;
+ document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
+ clearInterval(intervalID);
+ intervalID = null;
+ };
+
+ mutex.locked = function() {
+ return !!intervalID;
+ };
+
+ return mutex;
+ }
+
+ function Graph(other, mutable) {
+ if (!(this instanceof Graph)) return new Graph(other, mutable);
+
+ if (other instanceof Graph) {
+ var base = other.base();
+ this.entities = _.assign(Object.create(base.entities), other.entities);
+ this._parentWays = _.assign(Object.create(base.parentWays), other._parentWays);
+ this._parentRels = _.assign(Object.create(base.parentRels), other._parentRels);
+
+ } else {
+ this.entities = Object.create({});
+ this._parentWays = Object.create({});
+ this._parentRels = Object.create({});
+ this.rebase(other || [], [this]);
+ }
+
+ this.transients = {};
+ this._childNodes = {};
+ this.frozen = !mutable;
+ }
+
+ Graph.prototype = {
+ hasEntity: function(id) {
+ return this.entities[id];
+ },
+
+ entity: function(id) {
+ var entity = this.entities[id];
+ if (!entity) {
+ throw new Error('entity ' + id + ' not found');
+ }
+ return entity;
+ },
+
+ transient: function(entity, key, fn) {
+ var id = entity.id,
+ transients = this.transients[id] ||
+ (this.transients[id] = {});
+
+ if (transients[key] !== undefined) {
+ return transients[key];
+ }
+
+ transients[key] = fn.call(entity);
+
+ return transients[key];
+ },
+
+ parentWays: function(entity) {
+ var parents = this._parentWays[entity.id],
+ result = [];
+
+ if (parents) {
+ for (var i = 0; i < parents.length; i++) {
+ result.push(this.entity(parents[i]));
+ }
+ }
+ return result;
+ },
+
+ isPoi: function(entity) {
+ var parentWays = this._parentWays[entity.id];
+ return !parentWays || parentWays.length === 0;
+ },
+
+ isShared: function(entity) {
+ var parentWays = this._parentWays[entity.id];
+ return parentWays && parentWays.length > 1;
+ },
+
+ parentRelations: function(entity) {
+ var parents = this._parentRels[entity.id],
+ result = [];
+
+ if (parents) {
+ for (var i = 0; i < parents.length; i++) {
+ result.push(this.entity(parents[i]));
+ }
+ }
+ return result;
+ },
+
+ childNodes: function(entity) {
+ if (this._childNodes[entity.id]) return this._childNodes[entity.id];
+ if (!entity.nodes) return [];
+
+ var nodes = [];
+ for (var i = 0; i < entity.nodes.length; i++) {
+ nodes[i] = this.entity(entity.nodes[i]);
+ }
+
+ if (iD.debug) Object.freeze(nodes);
+
+ this._childNodes[entity.id] = nodes;
+ return this._childNodes[entity.id];
+ },
+
+ base: function() {
+ return {
+ 'entities': getPrototypeOf(this.entities),
+ 'parentWays': getPrototypeOf(this._parentWays),
+ 'parentRels': getPrototypeOf(this._parentRels)
+ };
+ },
+
+ // Unlike other graph methods, rebase mutates in place. This is because it
+ // is used only during the history operation that merges newly downloaded
+ // data into each state. To external consumers, it should appear as if the
+ // graph always contained the newly downloaded data.
+ rebase: function(entities, stack, force) {
+ var base = this.base(),
+ i, j, k, id;
+
+ for (i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+
+ if (!entity.visible || (!force && base.entities[entity.id]))
+ continue;
+
+ // Merging data into the base graph
+ base.entities[entity.id] = entity;
+ this._updateCalculated(undefined, entity, base.parentWays, base.parentRels);
+
+ // Restore provisionally-deleted nodes that are discovered to have an extant parent
+ if (entity.type === 'way') {
+ for (j = 0; j < entity.nodes.length; j++) {
+ id = entity.nodes[j];
+ for (k = 1; k < stack.length; k++) {
+ var ents = stack[k].entities;
+ if (ents.hasOwnProperty(id) && ents[id] === undefined) {
+ delete ents[id];
+ }
+ }
+ }
+ }
+ }
+
+ for (i = 0; i < stack.length; i++) {
+ stack[i]._updateRebased();
+ }
+ },
+
+ _updateRebased: function() {
+ var base = this.base(),
+ i, k, child, id, keys;
+
+ keys = Object.keys(this._parentWays);
+ for (i = 0; i < keys.length; i++) {
+ child = keys[i];
+ if (base.parentWays[child]) {
+ for (k = 0; k < base.parentWays[child].length; k++) {
+ id = base.parentWays[child][k];
+ if (!this.entities.hasOwnProperty(id) && !_.includes(this._parentWays[child], id)) {
+ this._parentWays[child].push(id);
+ }
+ }
+ }
+ }
+
+ keys = Object.keys(this._parentRels);
+ for (i = 0; i < keys.length; i++) {
+ child = keys[i];
+ if (base.parentRels[child]) {
+ for (k = 0; k < base.parentRels[child].length; k++) {
+ id = base.parentRels[child][k];
+ if (!this.entities.hasOwnProperty(id) && !_.includes(this._parentRels[child], id)) {
+ this._parentRels[child].push(id);
+ }
+ }
+ }
+ }
+
+ this.transients = {};
+
+ // this._childNodes is not updated, under the assumption that
+ // ways are always downloaded with their child nodes.
+ },
+
+ // Updates calculated properties (parentWays, parentRels) for the specified change
+ _updateCalculated: function(oldentity, entity, parentWays, parentRels) {
+
+ parentWays = parentWays || this._parentWays;
+ parentRels = parentRels || this._parentRels;
+
+ var type = entity && entity.type || oldentity && oldentity.type,
+ removed, added, ways, rels, i;
+
+
+ if (type === 'way') {
+
+ // Update parentWays
+ if (oldentity && entity) {
+ removed = _.difference(oldentity.nodes, entity.nodes);
+ added = _.difference(entity.nodes, oldentity.nodes);
+ } else if (oldentity) {
+ removed = oldentity.nodes;
+ added = [];
+ } else if (entity) {
+ removed = [];
+ added = entity.nodes;
+ }
+ for (i = 0; i < removed.length; i++) {
+ parentWays[removed[i]] = _.without(parentWays[removed[i]], oldentity.id);
+ }
+ for (i = 0; i < added.length; i++) {
+ ways = _.without(parentWays[added[i]], entity.id);
+ ways.push(entity.id);
+ parentWays[added[i]] = ways;
+ }
+
+ } else if (type === 'relation') {
+
+ // Update parentRels
+ if (oldentity && entity) {
+ removed = _.difference(oldentity.members, entity.members);
+ added = _.difference(entity.members, oldentity);
+ } else if (oldentity) {
+ removed = oldentity.members;
+ added = [];
+ } else if (entity) {
+ removed = [];
+ added = entity.members;
+ }
+ for (i = 0; i < removed.length; i++) {
+ parentRels[removed[i].id] = _.without(parentRels[removed[i].id], oldentity.id);
+ }
+ for (i = 0; i < added.length; i++) {
+ rels = _.without(parentRels[added[i].id], entity.id);
+ rels.push(entity.id);
+ parentRels[added[i].id] = rels;
+ }
+ }
+ },
+
+ replace: function(entity) {
+ if (this.entities[entity.id] === entity)
+ return this;
+
+ return this.update(function() {
+ this._updateCalculated(this.entities[entity.id], entity);
+ this.entities[entity.id] = entity;
+ });
+ },
+
+ remove: function(entity) {
+ return this.update(function() {
+ this._updateCalculated(entity, undefined);
+ this.entities[entity.id] = undefined;
+ });
+ },
+
+ revert: function(id) {
+ var baseEntity = this.base().entities[id],
+ headEntity = this.entities[id];
+
+ if (headEntity === baseEntity)
+ return this;
+
+ return this.update(function() {
+ this._updateCalculated(headEntity, baseEntity);
+ delete this.entities[id];
+ });
+ },
+
+ update: function() {
+ var graph = this.frozen ? Graph(this, true) : this;
+
+ for (var i = 0; i < arguments.length; i++) {
+ arguments[i].call(graph, graph);
+ }
+
+ if (this.frozen) graph.frozen = true;
+
+ return graph;
+ },
+
+ // Obliterates any existing entities
+ load: function(entities) {
+ var base = this.base();
+ this.entities = Object.create(base.entities);
+
+ for (var i in entities) {
+ this.entities[i] = entities[i];
+ this._updateCalculated(base.entities[i], this.entities[i]);
+ }
+
+ return this;
+ }
+ };
+
+ function Tree(head) {
+ var rtree = rbush(),
+ rectangles = {};
+
+ function entityRectangle(entity) {
+ var rect = entity.extent(head).rectangle();
+ rect.id = entity.id;
+ rectangles[entity.id] = rect;
+ return rect;
+ }
+
+ function updateParents(entity, insertions, memo) {
+ head.parentWays(entity).forEach(function(way) {
+ if (rectangles[way.id]) {
+ rtree.remove(rectangles[way.id]);
+ insertions[way.id] = way;
+ }
+ updateParents(way, insertions, memo);
+ });
+
+ head.parentRelations(entity).forEach(function(relation) {
+ if (memo[entity.id]) return;
+ memo[entity.id] = true;
+ if (rectangles[relation.id]) {
+ rtree.remove(rectangles[relation.id]);
+ insertions[relation.id] = relation;
+ }
+ updateParents(relation, insertions, memo);
+ });
+ }
+
+ var tree = {};
+
+ tree.rebase = function(entities, force) {
+ var insertions = {};
+
+ for (var i = 0; i < entities.length; i++) {
+ var entity = entities[i];
+
+ if (!entity.visible)
+ continue;
+
+ if (head.entities.hasOwnProperty(entity.id) || rectangles[entity.id]) {
+ if (!force) {
+ continue;
+ } else if (rectangles[entity.id]) {
+ rtree.remove(rectangles[entity.id]);
+ }
+ }
+
+ insertions[entity.id] = entity;
+ updateParents(entity, insertions, {});
+ }
+
+ rtree.load(_.map(insertions, entityRectangle));
+
+ return tree;
+ };
+
+ tree.intersects = function(extent, graph) {
+ if (graph !== head) {
+ var diff = Difference(head, graph),
+ insertions = {};
+
+ head = graph;
+
+ diff.deleted().forEach(function(entity) {
+ rtree.remove(rectangles[entity.id]);
+ delete rectangles[entity.id];
+ });
+
+ diff.modified().forEach(function(entity) {
+ rtree.remove(rectangles[entity.id]);
+ insertions[entity.id] = entity;
+ updateParents(entity, insertions, {});
+ });
+
+ diff.created().forEach(function(entity) {
+ insertions[entity.id] = entity;
+ });
+
+ rtree.load(_.map(insertions, entityRectangle));
+ }
+
+ return rtree.search(extent.rectangle()).map(function(rect) {
+ return head.entity(rect.id);
+ });
+ };
+
+ return tree;
+ }
+
+ function modalModule(selection, blocking) {
+ var keybinding = d3.keybinding('modal');
+ var previous = selection.select('div.modal');
+ var animate = previous.empty();
+
+ previous.transition()
+ .duration(200)
+ .style('opacity', 0)
+ .remove();
+
+ var shaded = selection
+ .append('div')
+ .attr('class', 'shaded')
+ .style('opacity', 0);
+
+ shaded.close = function() {
+ shaded
+ .transition()
+ .duration(200)
+ .style('opacity',0)
+ .remove();
+ modal
+ .transition()
+ .duration(200)
+ .style('top','0px');
+
+ keybinding.off();
+ };
+
+
+ var modal = shaded.append('div')
+ .attr('class', 'modal fillL col6');
+
+ if (!blocking) {
+ shaded.on('click.remove-modal', function() {
+ if (d3.event.target === this) {
+ shaded.close();
+ }
+ });
+
+ modal.append('button')
+ .attr('class', 'close')
+ .on('click', shaded.close)
+ .call(iD.svg.Icon('#icon-close'));
+
+ keybinding
+ .on('⌫', shaded.close)
+ .on('⎋', shaded.close);
+
+ d3.select(document).call(keybinding);
+ }
+
+ modal.append('div')
+ .attr('class', 'content');
+
+ if (animate) {
+ shaded.transition().style('opacity', 1);
+ } else {
+ shaded.style('opacity', 1);
+ }
+
+ return shaded;
+ }
+
+ function Loading(context) {
+ var message = '',
+ blocking = false,
+ modal;
+
+ var loading = function(selection) {
+ modal = modalModule(selection, blocking);
+
+ var loadertext = modal.select('.content')
+ .classed('loading-modal', true)
+ .append('div')
+ .attr('class', 'modal-section fillL');
+
+ loadertext.append('img')
+ .attr('class', 'loader')
+ .attr('src', context.imagePath('loader-white.gif'));
+
+ loadertext.append('h3')
+ .text(message);
+
+ modal.select('button.close')
+ .attr('class', 'hide');
+
+ return loading;
+ };
+
+ loading.message = function(_) {
+ if (!arguments.length) return message;
+ message = _;
+ return loading;
+ };
+
+ loading.blocking = function(_) {
+ if (!arguments.length) return blocking;
+ blocking = _;
+ return loading;
+ };
+
+ loading.close = function() {
+ modal.remove();
+ };
+
+ return loading;
+ }
+
+ function History(context) {
+ var stack, index, tree,
+ imageryUsed = ['Bing'],
+ dispatch = d3.dispatch('change', 'undone', 'redone'),
+ lock = SessionMutex('lock');
+
+ function perform(actions) {
+ actions = Array.prototype.slice.call(actions);
+
+ var annotation;
+
+ if (!_.isFunction(_.last(actions))) {
+ annotation = actions.pop();
+ }
+
+ var graph = stack[index].graph;
+ for (var i = 0; i < actions.length; i++) {
+ graph = actions[i](graph);
+ }
+
+ return {
+ graph: graph,
+ annotation: annotation,
+ imageryUsed: imageryUsed
+ };
+ }
+
+ function change(previous) {
+ var difference = Difference(previous, history.graph());
+ dispatch.change(difference);
+ return difference;
+ }
+
+ // iD uses namespaced keys so multiple installations do not conflict
+ function getKey(n) {
+ return 'iD_' + window.location.origin + '_' + n;
+ }
+
+ var history = {
+ graph: function() {
+ return stack[index].graph;
+ },
+
+ base: function() {
+ return stack[0].graph;
+ },
+
+ merge: function(entities, extent) {
+ stack[0].graph.rebase(entities, _.map(stack, 'graph'), false);
+ tree.rebase(entities, false);
+
+ dispatch.change(undefined, extent);
+ },
+
+ perform: function() {
+ var previous = stack[index].graph;
+
+ stack = stack.slice(0, index + 1);
+ stack.push(perform(arguments));
+ index++;
+
+ return change(previous);
+ },
+
+ replace: function() {
+ var previous = stack[index].graph;
+
+ // assert(index == stack.length - 1)
+ stack[index] = perform(arguments);
+
+ return change(previous);
+ },
+
+ pop: function() {
+ var previous = stack[index].graph;
+
+ if (index > 0) {
+ index--;
+ stack.pop();
+ return change(previous);
+ }
+ },
+
+ // 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() {
+ var previous = stack[index].graph;
+
+ // Pop to the next annotated state.
+ while (index > 0) {
+ index--;
+ if (stack[index].annotation) break;
+ }
+
+ dispatch.undone();
+ return change(previous);
+ },
+
+ redo: function() {
+ var previous = stack[index].graph;
+
+ while (index < stack.length - 1) {
+ index++;
+ if (stack[index].annotation) break;
+ }
+
+ dispatch.redone();
+ return change(previous);
+ },
+
+ undoAnnotation: function() {
+ var i = index;
+ while (i >= 0) {
+ if (stack[i].annotation) return stack[i].annotation;
+ i--;
+ }
+ },
+
+ redoAnnotation: function() {
+ var i = index + 1;
+ while (i <= stack.length - 1) {
+ if (stack[i].annotation) return stack[i].annotation;
+ i++;
+ }
+ },
+
+ intersects: function(extent) {
+ return tree.intersects(extent, stack[index].graph);
+ },
+
+ difference: function() {
+ var base = stack[0].graph,
+ head = stack[index].graph;
+ return Difference(base, head);
+ },
+
+ changes: function(action) {
+ var base = stack[0].graph,
+ head = stack[index].graph;
+
+ if (action) {
+ head = action(head);
+ }
+
+ var difference = Difference(base, head);
+
+ return {
+ modified: difference.modified(),
+ created: difference.created(),
+ deleted: difference.deleted()
+ };
+ },
+
+ validate: function(changes) {
+ return _(iD.validations)
+ .map(function(fn) { return fn()(changes, stack[index].graph); })
+ .flatten()
+ .value();
+ },
+
+ hasChanges: function() {
+ return this.difference().length() > 0;
+ },
+
+ imageryUsed: function(sources) {
+ if (sources) {
+ imageryUsed = sources;
+ return history;
+ } else {
+ return _(stack.slice(1, index + 1))
+ .map('imageryUsed')
+ .flatten()
+ .uniq()
+ .without(undefined, 'Custom')
+ .value();
+ }
+ },
+
+ reset: function() {
+ stack = [{graph: Graph()}];
+ index = 0;
+ tree = Tree(stack[0].graph);
+ dispatch.change();
+ return history;
+ },
+
+ toJSON: function() {
+ if (!this.hasChanges()) return;
+
+ var allEntities = {},
+ baseEntities = {},
+ base = stack[0];
+
+ var s = stack.map(function(i) {
+ var modified = [], deleted = [];
+
+ _.forEach(i.graph.entities, function(entity, id) {
+ if (entity) {
+ var key = Entity.key(entity);
+ allEntities[key] = entity;
+ modified.push(key);
+ } else {
+ deleted.push(id);
+ }
+
+ // make sure that the originals of changed or deleted entities get merged
+ // into the base of the stack after restoring the data from JSON.
+ if (id in base.graph.entities) {
+ baseEntities[id] = base.graph.entities[id];
+ }
+ // get originals of parent entities too
+ _.forEach(base.graph._parentWays[id], function(parentId) {
+ if (parentId in base.graph.entities) {
+ baseEntities[parentId] = base.graph.entities[parentId];
+ }
+ });
+ });
+
+ var x = {};
+
+ if (modified.length) x.modified = modified;
+ if (deleted.length) x.deleted = deleted;
+ if (i.imageryUsed) x.imageryUsed = i.imageryUsed;
+ if (i.annotation) x.annotation = i.annotation;
+
+ return x;
+ });
+
+ return JSON.stringify({
+ version: 3,
+ entities: _.values(allEntities),
+ baseEntities: _.values(baseEntities),
+ stack: s,
+ nextIDs: Entity.id.next,
+ index: index
+ });
+ },
+
+ fromJSON: function(json, loadChildNodes) {
+ var h = JSON.parse(json),
+ loadComplete = true;
+
+ Entity.id.next = h.nextIDs;
+ index = h.index;
+
+ if (h.version === 2 || h.version === 3) {
+ var allEntities = {};
+
+ h.entities.forEach(function(entity) {
+ allEntities[Entity.key(entity)] = Entity(entity);
+ });
+
+ if (h.version === 3) {
+ // This merges originals for changed entities into the base of
+ // the stack even if the current stack doesn't have them (for
+ // example when iD has been restarted in a different region)
+ var baseEntities = h.baseEntities.map(function(d) { return Entity(d); });
+ stack[0].graph.rebase(baseEntities, _.map(stack, 'graph'), true);
+ tree.rebase(baseEntities, true);
+
+ // When we restore a modified way, we also need to fetch any missing
+ // childnodes that would normally have been downloaded with it.. #2142
+ if (loadChildNodes) {
+ var missing = _(baseEntities)
+ .filter({ type: 'way' })
+ .map('nodes')
+ .flatten()
+ .uniq()
+ .reject(function(n) { return stack[0].graph.hasEntity(n); })
+ .value();
+
+ if (!_.isEmpty(missing)) {
+ loadComplete = false;
+ context.redrawEnable(false);
+
+ var loading = Loading(context).blocking(true);
+ context.container().call(loading);
+
+ var childNodesLoaded = function(err, result) {
+ if (!err) {
+ var visible = _.groupBy(result.data, 'visible');
+ if (!_.isEmpty(visible.true)) {
+ missing = _.difference(missing, _.map(visible.true, 'id'));
+ stack[0].graph.rebase(visible.true, _.map(stack, 'graph'), true);
+ tree.rebase(visible.true, true);
+ }
+
+ // fetch older versions of nodes that were deleted..
+ _.each(visible.false, function(entity) {
+ context.connection()
+ .loadEntityVersion(entity.id, +entity.version - 1, childNodesLoaded);
+ });
+ }
+
+ if (err || _.isEmpty(missing)) {
+ loading.close();
+ context.redrawEnable(true);
+ dispatch.change();
+ }
+ };
+
+ context.connection().loadMultiple(missing, childNodesLoaded);
+ }
+ }
+ }
+
+ stack = h.stack.map(function(d) {
+ var entities = {}, entity;
+
+ if (d.modified) {
+ d.modified.forEach(function(key) {
+ entity = allEntities[key];
+ entities[entity.id] = entity;
+ });
+ }
+
+ if (d.deleted) {
+ d.deleted.forEach(function(id) {
+ entities[id] = undefined;
+ });
+ }
+
+ return {
+ graph: Graph(stack[0].graph).load(entities),
+ annotation: d.annotation,
+ imageryUsed: d.imageryUsed
+ };
+ });
+
+ } else { // original version
+ stack = h.stack.map(function(d) {
+ var entities = {};
+
+ for (var i in d.entities) {
+ var entity = d.entities[i];
+ entities[i] = entity === 'undefined' ? undefined : Entity(entity);
+ }
+
+ d.graph = Graph(stack[0].graph).load(entities);
+ return d;
+ });
+ }
+
+ if (loadComplete) {
+ dispatch.change();
+ }
+
+ return history;
+ },
+
+ save: function() {
+ if (lock.locked()) context.storage(getKey('saved_history'), history.toJSON() || null);
+ return history;
+ },
+
+ clearSaved: function() {
+ context.debouncedSave.cancel();
+ if (lock.locked()) context.storage(getKey('saved_history'), null);
+ return history;
+ },
+
+ lock: function() {
+ return lock.lock();
+ },
+
+ unlock: function() {
+ lock.unlock();
+ },
+
+ // is iD not open in another window and it detects that
+ // there's a history stored in localStorage that's recoverable?
+ restorableChanges: function() {
+ return lock.locked() && !!context.storage(getKey('saved_history'));
+ },
+
+ // load history from a version stored in localStorage
+ restore: function() {
+ if (!lock.locked()) return;
+
+ var json = context.storage(getKey('saved_history'));
+ if (json) history.fromJSON(json, true);
+ },
+
+ _getKey: getKey
+
+ };
+
+ history.reset();
+
+ return d3.rebind(history, dispatch, 'on');
+ }
+
function Circularize(wayId
, projection, maxAngle) {
maxAngle = (maxAngle || 20) * Math.PI / 180;
@@ -2850,13 +4478,6 @@
return action;
}
- // wraps an index to an interval [0..length-1]
- function wrap(index, length) {
- if (index < 0)
- index += Math.ceil(-index/length)*length;
- return index % length;
- }
-
// Split a way at the given node.
//
// Optionally, split only the given ways, if multiple ways share
@@ -2891,8 +4512,8 @@
best = 0,
idxB;
- function wrap$$(index) {
- return wrap(index, nodes.length);
+ function wrap(index) {
+ return Wrap(index, nodes.length);
}
function dist(nA, nB) {
@@ -2901,14 +4522,14 @@
// calculate lengths
length = 0;
- for (i = wrap$$(idxA+1); i !== idxA; i = wrap$$(i+1)) {
- length += dist(nodes[i], nodes[wrap$$(i-1)]);
+ for (i = wrap(idxA+1); i !== idxA; i = wrap(i+1)) {
+ length += dist(nodes[i], nodes[wrap(i-1)]);
lengths[i] = length;
}
length = 0;
- for (i = wrap$$(idxA-1); i !== idxA; i = wrap$$(i-1)) {
- length += dist(nodes[i], nodes[wrap$$(i+1)]);
+ for (i = wrap(idxA-1); i !== idxA; i = wrap(i-1)) {
+ length += dist(nodes[i], nodes[wrap(i+1)]);
if (length < lengths[i])
lengths[i] = length;
}
@@ -3429,6 +5050,18 @@
});
exports.actions = actions;
+ exports.Connection = Connection;
+ exports.Difference = Difference;
+ exports.Entity = Entity;
+ exports.Graph = Graph;
+ exports.History = History;
+ exports.Node = Node;
+ exports.Relation = Relation;
+ exports.oneWayTags = oneWayTags;
+ exports.pavedTags = pavedTags;
+ exports.interestingTag = interestingTag;
+ exports.Tree = Tree;
+ exports.Way = Way;
Object.defineProperty(exports, '__esModule', { value: true });
diff --git a/modules/core/connection.js b/modules/core/connection.js
index ad44447b3..5c0916054 100644
--- a/modules/core/connection.js
+++ b/modules/core/connection.js
@@ -2,6 +2,7 @@ import { Entity } from './entity';
import { Way } from './way';
import { Relation } from './relation';
import { Node } from './node';
+import { Extent } from '../geo/index';
export function Connection(useHttps) {
if (typeof useHttps !== 'boolean') {
@@ -384,7 +385,7 @@ export function Connection(useHttps) {
return {
id: tile.toString(),
- extent: iD.geo.Extent(
+ extent: Extent(
projection.invert([x, y + ts]),
projection.invert([x + ts, y]))
};
diff --git a/modules/core/graph.js b/modules/core/graph.js
index a46200ba1..a5bf356b5 100644
--- a/modules/core/graph.js
+++ b/modules/core/graph.js
@@ -1,3 +1,5 @@
+import { getPrototypeOf } from '../util/index';
+
export function Graph(other, mutable) {
if (!(this instanceof Graph)) return new Graph(other, mutable);
@@ -97,9 +99,9 @@ Graph.prototype = {
base: function() {
return {
- 'entities': iD.util.getPrototypeOf(this.entities),
- 'parentWays': iD.util.getPrototypeOf(this._parentWays),
- 'parentRels': iD.util.getPrototypeOf(this._parentRels)
+ 'entities': getPrototypeOf(this.entities),
+ 'parentWays': getPrototypeOf(this._parentWays),
+ 'parentRels': getPrototypeOf(this._parentRels)
};
},
diff --git a/modules/core/history.js b/modules/core/history.js
index ad9ae1243..fc4b36b58 100644
--- a/modules/core/history.js
+++ b/modules/core/history.js
@@ -2,12 +2,14 @@ import { Entity } from './entity';
import { Graph } from './graph';
import { Difference } from './difference';
import { Tree } from './tree';
+import { SessionMutex } from '../util/index';
+import { Loading } from '../ui/core/index';
export function History(context) {
var stack, index, tree,
imageryUsed = ['Bing'],
dispatch = d3.dispatch('change', 'undone', 'redone'),
- lock = iD.util.SessionMutex('lock');
+ lock = SessionMutex('lock');
function perform(actions) {
actions = Array.prototype.slice.call(actions);
@@ -291,7 +293,7 @@ export function History(context) {
loadComplete = false;
context.redrawEnable(false);
- var loading = iD.ui.Loading(context).blocking(true);
+ var loading = Loading(context).blocking(true);
context.container().call(loading);
var childNodesLoaded = function(err, result) {
diff --git a/modules/core/node.js b/modules/core/node.js
index c01375e45..929769141 100644
--- a/modules/core/node.js
+++ b/modules/core/node.js
@@ -1,5 +1,7 @@
// iD.Node = iD.Entity.node;
import { Entity } from './entity';
+import { Extent } from '../geo/index';
+
export function Node() {
if (!(this instanceof Node)) {
return (new Node()).initialize(arguments);
@@ -16,7 +18,7 @@ _.extend(Node.prototype, {
type: 'node',
extent: function() {
- return new iD.geo.Extent(this.loc);
+ return new Extent(this.loc);
},
geometry: function(graph) {
diff --git a/modules/core/relation.js b/modules/core/relation.js
index d9670cdac..395b962d8 100644
--- a/modules/core/relation.js
+++ b/modules/core/relation.js
@@ -1,4 +1,5 @@
import { Entity } from './entity';
+import { Extent, joinWays, polygonContainsPolygon, polygonIntersectsPolygon } from '../geo/index';
export function Relation() {
if (!(this instanceof Relation)) {
@@ -41,11 +42,11 @@ _.extend(Relation.prototype, {
extent: function(resolver, memo) {
return resolver.transient(this, 'extent', function() {
- if (memo && memo[this.id]) return iD.geo.Extent();
+ if (memo && memo[this.id]) return Extent();
memo = memo || {};
memo[this.id] = true;
- var extent = iD.geo.Extent();
+ var extent = Extent();
for (var i = 0; i < this.members.length; i++) {
var member = resolver.hasEntity(this.members[i].id);
if (member) {
@@ -224,8 +225,8 @@ _.extend(Relation.prototype, {
var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); }),
inners = this.members.filter(function(m) { return 'inner' === m.role; });
- outers = iD.geo.joinWays(outers, resolver);
- inners = iD.geo.joinWays(inners, resolver);
+ outers = joinWays(outers, resolver);
+ inners = joinWays(inners, resolver);
outers = outers.map(function(outer) { return _.map(outer.nodes, 'loc'); });
inners = inners.map(function(inner) { return _.map(inner.nodes, 'loc'); });
@@ -241,13 +242,13 @@ _.extend(Relation.prototype, {
for (o = 0; o < outers.length; o++) {
outer = outers[o];
- if (iD.geo.polygonContainsPolygon(outer, inner))
+ if (polygonContainsPolygon(outer, inner))
return o;
}
for (o = 0; o < outers.length; o++) {
outer = outers[o];
- if (iD.geo.polygonIntersectsPolygon(outer, inner))
+ if (polygonIntersectsPolygon(outer, inner))
return o;
}
}
diff --git a/modules/core/way.js b/modules/core/way.js
index f7788e2ff..dfcb455c9 100644
--- a/modules/core/way.js
+++ b/modules/core/way.js
@@ -1,5 +1,7 @@
import { Entity } from './entity';
import { oneWayTags } from './tags';
+import { cross, Extent } from '../geo/index';
+
export function Way() {
if (!(this instanceof Way)) {
return (new Way()).initialize(arguments);
@@ -34,7 +36,7 @@ _.extend(Way.prototype, {
extent: function(resolver) {
return resolver.transient(this, 'extent', function() {
- var extent = iD.geo.Extent();
+ var extent = Extent();
for (var i = 0; i < this.nodes.length; i++) {
var node = resolver.hasEntity(this.nodes[i]);
if (node) {
@@ -113,7 +115,7 @@ _.extend(Way.prototype, {
var o = coords[(i+1) % coords.length],
a = coords[i],
b = coords[(i+2) % coords.length],
- res = iD.geo.cross(o, a, b);
+ res = cross(o, a, b);
curr = (res > 0) ? 1 : (res < 0) ? -1 : 0;
if (curr === 0) {
diff --git a/modules/index.js b/modules/index.js
index 872dd5f3b..352e9f8d8 100644
--- a/modules/index.js
+++ b/modules/index.js
@@ -1,5 +1,16 @@
import * as actions from './actions/index';
+export { Connection } from './core/connection';
+export { Difference } from './core/difference';
+export { Entity } from './core/entity';
+export { Graph } from './core/graph';
+export { History } from './core/history';
+export { Node } from './core/node';
+export { Relation } from './core/relation';
+export { oneWayTags, pavedTags, interestingTag } from './core/tags';
+export { Tree } from './core/tree';
+export { Way } from './core/way';
+
export {
actions
};
diff --git a/test/index.html b/test/index.html
index b2c9edca0..a64636118 100644
--- a/test/index.html
+++ b/test/index.html
@@ -42,7 +42,6 @@
-