mirror of
https://github.com/FoggedLens/iD.git
synced 2026-02-13 01:02:58 +00:00
Introduce real Entity subclasses
This commit is contained in:
2
API.md
2
API.md
@@ -39,7 +39,7 @@ A **line** is a way that is not an area. Elements representing lines have a `.li
|
||||
class. Since a line is also a way, they also have a `.way` class.
|
||||
|
||||
An **area** is a way that is circular, has certain tags, or lacks certain other
|
||||
tags (see `iD.Way.isArea` for the exact definition). Elements representing areas
|
||||
tags (see `iD.Way#isArea` for the exact definition). Elements representing areas
|
||||
have an `.area` class. Since an area is also a way, they also have a `.way` class.
|
||||
|
||||
### Tag classes
|
||||
|
||||
@@ -1,30 +1,12 @@
|
||||
iD.Entity = function(a, b, c) {
|
||||
if (!(this instanceof iD.Entity)) return new iD.Entity(a, b, c);
|
||||
iD.Entity = function(attrs) {
|
||||
// For prototypal inheritance.
|
||||
if (this instanceof iD.Entity) return;
|
||||
|
||||
this.tags = {};
|
||||
// Create the appropriate subtype.
|
||||
if (attrs && attrs.type) return iD.Entity[attrs.type].apply(this, arguments);
|
||||
|
||||
var sources = [a, b, c], source;
|
||||
for (var i = 0; i < sources.length; ++i) {
|
||||
source = sources[i];
|
||||
for (var prop in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||
this[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.id && this.type) {
|
||||
this.id = iD.Entity.id(this.type);
|
||||
this._updated = true;
|
||||
}
|
||||
|
||||
if (iD.debug) {
|
||||
Object.freeze(this);
|
||||
Object.freeze(this.tags);
|
||||
|
||||
if (this.nodes) Object.freeze(this.nodes);
|
||||
if (this.members) Object.freeze(this.members);
|
||||
}
|
||||
// Initialize a generic Entity (used only in tests).
|
||||
return (new iD.Entity()).initialize(arguments);
|
||||
};
|
||||
|
||||
iD.Entity.id = function (type) {
|
||||
@@ -42,6 +24,34 @@ iD.Entity.id.toOSM = function (id) {
|
||||
};
|
||||
|
||||
iD.Entity.prototype = {
|
||||
tags: {},
|
||||
|
||||
initialize: function(sources) {
|
||||
for (var i = 0; i < sources.length; ++i) {
|
||||
var source = sources[i];
|
||||
for (var prop in source) {
|
||||
if (Object.prototype.hasOwnProperty.call(source, prop)) {
|
||||
this[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.id && this.type) {
|
||||
this.id = iD.Entity.id(this.type);
|
||||
this._updated = true;
|
||||
}
|
||||
|
||||
if (iD.debug) {
|
||||
Object.freeze(this);
|
||||
Object.freeze(this.tags);
|
||||
|
||||
if (this.nodes) Object.freeze(this.nodes);
|
||||
if (this.members) Object.freeze(this.members);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
osmId: function() {
|
||||
return iD.Entity.id.toOSM(this.id);
|
||||
},
|
||||
@@ -97,39 +107,54 @@ iD.Entity.prototype = {
|
||||
}
|
||||
};
|
||||
|
||||
iD.Node = function(attrs) {
|
||||
return iD.Entity(attrs || {}, {type: 'node'});
|
||||
iD.Entity.extend = function(properties) {
|
||||
var Subclass = function() {
|
||||
if (this instanceof Subclass) return;
|
||||
return (new Subclass()).initialize(arguments);
|
||||
};
|
||||
|
||||
Subclass.prototype = new iD.Entity();
|
||||
_.extend(Subclass.prototype, properties);
|
||||
iD.Entity[properties.type] = Subclass;
|
||||
|
||||
return Subclass;
|
||||
};
|
||||
|
||||
iD.Way = function(attrs) {
|
||||
return iD.Entity({nodes: []}, attrs || {}, {type: 'way'});
|
||||
};
|
||||
iD.Node = iD.Entity.extend({
|
||||
type: "node"
|
||||
});
|
||||
|
||||
iD.Way.isOneWay = function(d) {
|
||||
return !!(d.tags.oneway && d.tags.oneway === 'yes');
|
||||
};
|
||||
iD.Way = iD.Entity.extend({
|
||||
type: "way",
|
||||
nodes: [],
|
||||
|
||||
iD.Way.isClosed = function(d) {
|
||||
return (!d.nodes.length) || d.nodes[d.nodes.length - 1].id === d.nodes[0].id;
|
||||
};
|
||||
isOneWay: function() {
|
||||
return !!(this.tags.oneway && this.tags.oneway === 'yes');
|
||||
},
|
||||
|
||||
// a way is an area if:
|
||||
//
|
||||
// - area=yes
|
||||
// - closed and
|
||||
// - doesn't have area=no
|
||||
// - doesn't have highway tag
|
||||
iD.Way.isArea = function(d) {
|
||||
return (d.tags.area && d.tags.area === 'yes') ||
|
||||
(iD.Way.isClosed(d) &&
|
||||
// area-ness is disabled
|
||||
(!d.tags.area || d.tags.area !== 'no') &&
|
||||
// Tags that disable area-ness unless they are accompanied by
|
||||
// area=yes
|
||||
!d.tags.highway &&
|
||||
!d.tags.barrier);
|
||||
};
|
||||
isClosed: function() {
|
||||
return (!this.nodes.length) || this.nodes[this.nodes.length - 1] === this.nodes[0];
|
||||
},
|
||||
|
||||
iD.Relation = function(attrs) {
|
||||
return iD.Entity({members: []}, attrs || {}, {type: 'relation'});
|
||||
};
|
||||
// a way is an area if:
|
||||
//
|
||||
// - area=yes
|
||||
// - closed and
|
||||
// - doesn't have area=no
|
||||
// - doesn't have highway tag
|
||||
isArea: function() {
|
||||
return (this.tags.area && this.tags.area === 'yes') ||
|
||||
(this.isClosed() &&
|
||||
// area-ness is disabled
|
||||
(!this.tags.area || this.tags.area !== 'no') &&
|
||||
// Tags that disable area-ness unless they are accompanied by
|
||||
// area=yes
|
||||
!this.tags.highway &&
|
||||
!this.tags.barrier);
|
||||
}
|
||||
});
|
||||
|
||||
iD.Relation = iD.Entity.extend({
|
||||
type: "relation",
|
||||
members: []
|
||||
});
|
||||
|
||||
@@ -104,7 +104,7 @@ iD.Map = function() {
|
||||
if (a.type === 'way') {
|
||||
a._line = nodeline(a);
|
||||
ways.push(a);
|
||||
if (iD.Way.isArea(a)) areas.push(a);
|
||||
if (a.isArea()) areas.push(a);
|
||||
else lines.push(a);
|
||||
} else if (a._poi) {
|
||||
points.push(a);
|
||||
@@ -235,7 +235,7 @@ iD.Map = function() {
|
||||
|
||||
// Determine the lengths of oneway paths
|
||||
var lengths = {},
|
||||
oneways = strokes.filter(iD.Way.isOneWay).each(function(d) {
|
||||
oneways = strokes.filter(function (d) { return d.isOneWay(); }).each(function(d) {
|
||||
lengths[d.id] = Math.floor(this.getTotalLength() / alength);
|
||||
}).data();
|
||||
|
||||
|
||||
@@ -111,8 +111,10 @@
|
||||
|
||||
<script src="spec/graph/graph.js"></script>
|
||||
<script src="spec/graph/entity.js"></script>
|
||||
<script src="spec/graph/history.js"></script>
|
||||
<script src="spec/graph/node.js"></script>
|
||||
<script src="spec/graph/way.js"></script>
|
||||
<script src="spec/graph/relation.js"></script>
|
||||
<script src="spec/graph/history.js"></script>
|
||||
|
||||
<script src="spec/modes/add_point.js"></script>
|
||||
|
||||
|
||||
@@ -43,8 +43,10 @@
|
||||
|
||||
<script src="spec/graph/graph.js"></script>
|
||||
<script src="spec/graph/entity.js"></script>
|
||||
<script src="spec/graph/history.js"></script>
|
||||
<script src="spec/graph/node.js"></script>
|
||||
<script src="spec/graph/way.js"></script>
|
||||
<script src="spec/graph/relation.js"></script>
|
||||
<script src="spec/graph/history.js"></script>
|
||||
|
||||
<script src="spec/modes/add_point.js"></script>
|
||||
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
describe('iD.Entity', function () {
|
||||
it("returns a subclass of the appropriate type", function () {
|
||||
expect(iD.Entity({type: 'way'})).be.an.instanceOf(iD.Way);
|
||||
expect(iD.Entity({type: 'node'})).be.an.instanceOf(iD.Node);
|
||||
expect(iD.Entity({type: 'relation'})).be.an.instanceOf(iD.Relation);
|
||||
});
|
||||
|
||||
if (iD.debug) {
|
||||
it("is frozen", function () {
|
||||
expect(Object.isFrozen(iD.Entity())).to.be.true;
|
||||
@@ -41,6 +47,11 @@ describe('iD.Entity', function () {
|
||||
expect(e.tags).to.equal(tags);
|
||||
});
|
||||
|
||||
it("preserves existing attributes", function () {
|
||||
var e = iD.Entity({id: 'w1'}).update({});
|
||||
expect(e.id).to.equal('w1');
|
||||
});
|
||||
|
||||
it("tags the entity as updated", function () {
|
||||
var tags = {foo: 'bar'},
|
||||
e = iD.Entity().update({tags: tags});
|
||||
@@ -112,126 +123,3 @@ describe('iD.Entity', function () {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('iD.Node', function () {
|
||||
it("returns a node", function () {
|
||||
expect(iD.Node().type).to.equal("node");
|
||||
});
|
||||
|
||||
it("returns a created Entity if no ID is specified", function () {
|
||||
expect(iD.Node().created()).to.be.ok;
|
||||
});
|
||||
|
||||
it("returns an unmodified Entity if ID is specified", function () {
|
||||
expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok;
|
||||
expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok;
|
||||
});
|
||||
|
||||
it("defaults tags to an empty object", function () {
|
||||
expect(iD.Node().tags).to.eql({});
|
||||
});
|
||||
|
||||
it("sets tags as specified", function () {
|
||||
expect(iD.Node({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
|
||||
});
|
||||
|
||||
describe("#intersects", function () {
|
||||
it("returns true for a node within the given extent", function () {
|
||||
expect(iD.Node({loc: [0, 0]}).intersects([[-180, 90], [180, -90]])).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns false for a node outside the given extend", function () {
|
||||
expect(iD.Node({loc: [0, 0]}).intersects([[100, 90], [180, -90]])).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('iD.Way', function () {
|
||||
if (iD.debug) {
|
||||
it("freezes nodes", function () {
|
||||
expect(Object.isFrozen(iD.Way().nodes)).to.be.true;
|
||||
});
|
||||
}
|
||||
|
||||
it("returns a way", function () {
|
||||
expect(iD.Way().type).to.equal("way");
|
||||
});
|
||||
|
||||
it("returns a created Entity if no ID is specified", function () {
|
||||
expect(iD.Way().created()).to.be.ok;
|
||||
});
|
||||
|
||||
it("returns an unmodified Entity if ID is specified", function () {
|
||||
expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok;
|
||||
expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok;
|
||||
});
|
||||
|
||||
it("defaults nodes to an empty array", function () {
|
||||
expect(iD.Way().nodes).to.eql([]);
|
||||
});
|
||||
|
||||
it("sets nodes as specified", function () {
|
||||
expect(iD.Way({nodes: ["n-1"]}).nodes).to.eql(["n-1"]);
|
||||
});
|
||||
|
||||
it("defaults tags to an empty object", function () {
|
||||
expect(iD.Way().tags).to.eql({});
|
||||
});
|
||||
|
||||
it("sets tags as specified", function () {
|
||||
expect(iD.Way({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
|
||||
});
|
||||
|
||||
describe("#intersects", function () {
|
||||
it("returns true for a way with a node within the given extent", function () {
|
||||
var node = iD.Node({loc: [0, 0]}),
|
||||
way = iD.Way({nodes: [node.id]}),
|
||||
graph = iD.Graph([node, way]);
|
||||
expect(way.intersects([[-180, 90], [180, -90]], graph)).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns false for way with no nodes within the given extent", function () {
|
||||
var node = iD.Node({loc: [0, 0]}),
|
||||
way = iD.Way({nodes: [node.id]}),
|
||||
graph = iD.Graph([node, way]);
|
||||
expect(way.intersects([[100, 90], [180, -90]], graph)).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('iD.Relation', function () {
|
||||
if (iD.debug) {
|
||||
it("freezes nodes", function () {
|
||||
expect(Object.isFrozen(iD.Relation().members)).to.be.true;
|
||||
});
|
||||
}
|
||||
|
||||
it("returns a relation", function () {
|
||||
expect(iD.Relation().type).to.equal("relation");
|
||||
});
|
||||
|
||||
it("returns a created Entity if no ID is specified", function () {
|
||||
expect(iD.Relation().created()).to.be.ok;
|
||||
});
|
||||
|
||||
it("returns an unmodified Entity if ID is specified", function () {
|
||||
expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok;
|
||||
expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok;
|
||||
});
|
||||
|
||||
it("defaults members to an empty array", function () {
|
||||
expect(iD.Relation().members).to.eql([]);
|
||||
});
|
||||
|
||||
it("sets members as specified", function () {
|
||||
expect(iD.Relation({members: ["n-1"]}).members).to.eql(["n-1"]);
|
||||
});
|
||||
|
||||
it("defaults tags to an empty object", function () {
|
||||
expect(iD.Relation().tags).to.eql({});
|
||||
});
|
||||
|
||||
it("sets tags as specified", function () {
|
||||
expect(iD.Relation({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
|
||||
});
|
||||
});
|
||||
|
||||
33
test/spec/graph/node.js
Normal file
33
test/spec/graph/node.js
Normal file
@@ -0,0 +1,33 @@
|
||||
describe('iD.Node', function () {
|
||||
it("returns a node", function () {
|
||||
expect(iD.Node()).to.be.an.instanceOf(iD.Node);
|
||||
expect(iD.Node().type).to.equal("node");
|
||||
});
|
||||
|
||||
it("returns a created Entity if no ID is specified", function () {
|
||||
expect(iD.Node().created()).to.be.ok;
|
||||
});
|
||||
|
||||
it("returns an unmodified Entity if ID is specified", function () {
|
||||
expect(iD.Node({id: 'n1234'}).created()).not.to.be.ok;
|
||||
expect(iD.Node({id: 'n1234'}).modified()).not.to.be.ok;
|
||||
});
|
||||
|
||||
it("defaults tags to an empty object", function () {
|
||||
expect(iD.Node().tags).to.eql({});
|
||||
});
|
||||
|
||||
it("sets tags as specified", function () {
|
||||
expect(iD.Node({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
|
||||
});
|
||||
|
||||
describe("#intersects", function () {
|
||||
it("returns true for a node within the given extent", function () {
|
||||
expect(iD.Node({loc: [0, 0]}).intersects([[-180, 90], [180, -90]])).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns false for a node outside the given extend", function () {
|
||||
expect(iD.Node({loc: [0, 0]}).intersects([[100, 90], [180, -90]])).to.equal(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
37
test/spec/graph/relation.js
Normal file
37
test/spec/graph/relation.js
Normal file
@@ -0,0 +1,37 @@
|
||||
describe('iD.Relation', function () {
|
||||
if (iD.debug) {
|
||||
it("freezes nodes", function () {
|
||||
expect(Object.isFrozen(iD.Relation().members)).to.be.true;
|
||||
});
|
||||
}
|
||||
|
||||
it("returns a relation", function () {
|
||||
expect(iD.Relation()).to.be.an.instanceOf(iD.Relation);
|
||||
expect(iD.Relation().type).to.equal("relation");
|
||||
});
|
||||
|
||||
it("returns a created Entity if no ID is specified", function () {
|
||||
expect(iD.Relation().created()).to.be.ok;
|
||||
});
|
||||
|
||||
it("returns an unmodified Entity if ID is specified", function () {
|
||||
expect(iD.Relation({id: 'r1234'}).created()).not.to.be.ok;
|
||||
expect(iD.Relation({id: 'r1234'}).modified()).not.to.be.ok;
|
||||
});
|
||||
|
||||
it("defaults members to an empty array", function () {
|
||||
expect(iD.Relation().members).to.eql([]);
|
||||
});
|
||||
|
||||
it("sets members as specified", function () {
|
||||
expect(iD.Relation({members: ["n-1"]}).members).to.eql(["n-1"]);
|
||||
});
|
||||
|
||||
it("defaults tags to an empty object", function () {
|
||||
expect(iD.Relation().tags).to.eql({});
|
||||
});
|
||||
|
||||
it("sets tags as specified", function () {
|
||||
expect(iD.Relation({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
|
||||
});
|
||||
});
|
||||
@@ -1,24 +1,77 @@
|
||||
describe('Way', function() {
|
||||
describe('#isClosed', function() {
|
||||
it('is not closed with two distinct nodes', function() {
|
||||
var open_way = { type: 'way', nodes: [{id: 'n1'}, {id: 'n2'}] };
|
||||
expect(iD.Way.isClosed(open_way)).to.equal(false);
|
||||
describe('iD.Way', function() {
|
||||
if (iD.debug) {
|
||||
it("freezes nodes", function () {
|
||||
expect(Object.isFrozen(iD.Way().nodes)).to.be.true;
|
||||
});
|
||||
it('is not closed with a node loop', function() {
|
||||
var closed_way = { type: 'way', nodes: [{id: 'n1'}, {id: 'n2'}, {id: 'n1'}] };
|
||||
expect(iD.Way.isClosed(closed_way)).to.equal(true);
|
||||
}
|
||||
|
||||
it("returns a way", function () {
|
||||
expect(iD.Way()).to.be.an.instanceOf(iD.Way);
|
||||
expect(iD.Way().type).to.equal("way");
|
||||
});
|
||||
|
||||
it("returns a created Entity if no ID is specified", function () {
|
||||
expect(iD.Way().created()).to.be.ok;
|
||||
});
|
||||
|
||||
it("returns an unmodified Entity if ID is specified", function () {
|
||||
expect(iD.Way({id: 'w1234'}).created()).not.to.be.ok;
|
||||
expect(iD.Way({id: 'w1234'}).modified()).not.to.be.ok;
|
||||
});
|
||||
|
||||
it("defaults nodes to an empty array", function () {
|
||||
expect(iD.Way().nodes).to.eql([]);
|
||||
});
|
||||
|
||||
it("sets nodes as specified", function () {
|
||||
expect(iD.Way({nodes: ["n-1"]}).nodes).to.eql(["n-1"]);
|
||||
});
|
||||
|
||||
it("defaults tags to an empty object", function () {
|
||||
expect(iD.Way().tags).to.eql({});
|
||||
});
|
||||
|
||||
it("sets tags as specified", function () {
|
||||
expect(iD.Way({tags: {foo: 'bar'}}).tags).to.eql({foo: 'bar'});
|
||||
});
|
||||
|
||||
describe("#intersects", function () {
|
||||
it("returns true for a way with a node within the given extent", function () {
|
||||
var node = iD.Node({loc: [0, 0]}),
|
||||
way = iD.Way({nodes: [node.id]}),
|
||||
graph = iD.Graph([node, way]);
|
||||
expect(way.intersects([[-180, 90], [180, -90]], graph)).to.equal(true);
|
||||
});
|
||||
|
||||
it("returns false for way with no nodes within the given extent", function () {
|
||||
var node = iD.Node({loc: [0, 0]}),
|
||||
way = iD.Way({nodes: [node.id]}),
|
||||
graph = iD.Graph([node, way]);
|
||||
expect(way.intersects([[100, 90], [180, -90]], graph)).to.equal(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isClosed', function() {
|
||||
it('returns false when the way ends are not equal', function() {
|
||||
expect(iD.Way({nodes: ['n1', 'n2']}).isClosed()).to.equal(false);
|
||||
});
|
||||
|
||||
it('returns true when the way ends are equal', function() {
|
||||
expect(iD.Way({nodes: ['n1', 'n2', 'n1']}).isClosed()).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#isOneWay', function() {
|
||||
it('is not oneway without any tags', function() {
|
||||
expect(iD.Way.isOneWay(iD.Way())).to.eql(false);
|
||||
it('returns false when the way has no tags', function() {
|
||||
expect(iD.Way().isOneWay()).to.eql(false);
|
||||
});
|
||||
it('is not oneway oneway=no', function() {
|
||||
expect(iD.Way.isOneWay(iD.Way({ tags: { oneway: 'no' } }))).to.eql(false);
|
||||
|
||||
it('returns false when the way has tag oneway=no', function() {
|
||||
expect(iD.Way({tags: { oneway: 'no' }}).isOneWay()).to.equal(false);
|
||||
});
|
||||
it('is oneway oneway=yes', function() {
|
||||
expect(iD.Way.isOneWay(iD.Way({ tags: { oneway: 'yes' } }))).to.eql(true);
|
||||
|
||||
it('returns true when the way has tag oneway=yes', function() {
|
||||
expect(iD.Way({tags: { oneway: 'yes' }}).isOneWay()).to.equal(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user