mirror of
https://github.com/FoggedLens/iD.git
synced 2026-06-02 13:11:41 +02:00
Refactor Entity, Node, Relation, Tags, Way from core to osm
This commit is contained in:
+17
-20
@@ -1,16 +1,13 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
import { utilFunctor } from '../util/index';
|
||||
import osmAuth from 'osm-auth';
|
||||
import { JXON } from '../util/jxon';
|
||||
import { d3geoTile } from '../lib/d3.geo.tile';
|
||||
import { geoExtent } from '../geo/index';
|
||||
import { osmEntity, osmNode, osmRelation, osmWay } from '../osm/index';
|
||||
import { utilDetect } from '../util/detect';
|
||||
import { coreEntity } from './entity';
|
||||
import { coreNode } from './node';
|
||||
import { coreRelation } from './relation';
|
||||
import { coreWay } from './way';
|
||||
import { JXON } from '../util/jxon';
|
||||
import osmAuth from 'osm-auth';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
import { utilFunctor } from '../util/index';
|
||||
|
||||
|
||||
export function coreConnection(useHttps) {
|
||||
@@ -75,8 +72,8 @@ export function coreConnection(useHttps) {
|
||||
|
||||
|
||||
connection.loadEntity = function(id, callback) {
|
||||
var type = coreEntity.id.type(id),
|
||||
osmID = coreEntity.id.toOSM(id);
|
||||
var type = osmEntity.id.type(id),
|
||||
osmID = osmEntity.id.toOSM(id);
|
||||
|
||||
connection.loadFromURL(
|
||||
url + '/api/0.6/' + type + '/' + osmID + (type !== 'node' ? '/full' : ''),
|
||||
@@ -87,8 +84,8 @@ export function coreConnection(useHttps) {
|
||||
|
||||
|
||||
connection.loadEntityVersion = function(id, version, callback) {
|
||||
var type = coreEntity.id.type(id),
|
||||
osmID = coreEntity.id.toOSM(id);
|
||||
var type = osmEntity.id.type(id),
|
||||
osmID = osmEntity.id.toOSM(id);
|
||||
|
||||
connection.loadFromURL(
|
||||
url + '/api/0.6/' + type + '/' + osmID + '/' + version,
|
||||
@@ -99,9 +96,9 @@ export function coreConnection(useHttps) {
|
||||
|
||||
|
||||
connection.loadMultiple = function(ids, callback) {
|
||||
_.each(_.groupBy(_.uniq(ids), coreEntity.id.type), function(v, k) {
|
||||
_.each(_.groupBy(_.uniq(ids), osmEntity.id.type), function(v, k) {
|
||||
var type = k + 's',
|
||||
osmIDs = _.map(v, coreEntity.id.toOSM);
|
||||
osmIDs = _.map(v, osmEntity.id.toOSM);
|
||||
|
||||
_.each(_.chunk(osmIDs, 150), function(arr) {
|
||||
connection.loadFromURL(
|
||||
@@ -175,8 +172,8 @@ export function coreConnection(useHttps) {
|
||||
var parsers = {
|
||||
node: function nodeData(obj) {
|
||||
var attrs = obj.attributes;
|
||||
return new coreNode({
|
||||
id: coreEntity.id.fromOSM(nodeStr, attrs.id.value),
|
||||
return new osmNode({
|
||||
id: osmEntity.id.fromOSM(nodeStr, attrs.id.value),
|
||||
loc: getLoc(attrs),
|
||||
version: attrs.version.value,
|
||||
user: attrs.user && attrs.user.value,
|
||||
@@ -187,8 +184,8 @@ export function coreConnection(useHttps) {
|
||||
|
||||
way: function wayData(obj) {
|
||||
var attrs = obj.attributes;
|
||||
return new coreWay({
|
||||
id: coreEntity.id.fromOSM(wayStr, attrs.id.value),
|
||||
return new osmWay({
|
||||
id: osmEntity.id.fromOSM(wayStr, attrs.id.value),
|
||||
version: attrs.version.value,
|
||||
user: attrs.user && attrs.user.value,
|
||||
tags: getTags(obj),
|
||||
@@ -199,8 +196,8 @@ export function coreConnection(useHttps) {
|
||||
|
||||
relation: function relationData(obj) {
|
||||
var attrs = obj.attributes;
|
||||
return new coreRelation({
|
||||
id: coreEntity.id.fromOSM(relationStr, attrs.id.value),
|
||||
return new osmRelation({
|
||||
id: osmEntity.id.fromOSM(relationStr, attrs.id.value),
|
||||
version: attrs.version.value,
|
||||
user: attrs.user && attrs.user.value,
|
||||
tags: getTags(obj),
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { debug } from '../index';
|
||||
import { coreInterestingTag } from './tags';
|
||||
import { dataDeprecated } from '../../data/index';
|
||||
|
||||
|
||||
export function coreEntity(attrs) {
|
||||
// For prototypal inheritance.
|
||||
if (this instanceof coreEntity) return;
|
||||
|
||||
// Create the appropriate subtype.
|
||||
if (attrs && attrs.type) {
|
||||
return coreEntity[attrs.type].apply(this, arguments);
|
||||
} else if (attrs && attrs.id) {
|
||||
return coreEntity[coreEntity.id.type(attrs.id)].apply(this, arguments);
|
||||
}
|
||||
|
||||
// Initialize a generic Entity (used only in tests).
|
||||
return (new coreEntity()).initialize(arguments);
|
||||
}
|
||||
|
||||
|
||||
coreEntity.id = function(type) {
|
||||
return coreEntity.id.fromOSM(type, coreEntity.id.next[type]--);
|
||||
};
|
||||
|
||||
|
||||
coreEntity.id.next = {
|
||||
node: -1, way: -1, relation: -1
|
||||
};
|
||||
|
||||
|
||||
coreEntity.id.fromOSM = function(type, id) {
|
||||
return type[0] + id;
|
||||
};
|
||||
|
||||
|
||||
coreEntity.id.toOSM = function(id) {
|
||||
return id.slice(1);
|
||||
};
|
||||
|
||||
|
||||
coreEntity.id.type = function(id) {
|
||||
return { 'n': 'node', 'w': 'way', 'r': 'relation' }[id[0]];
|
||||
};
|
||||
|
||||
|
||||
// A function suitable for use as the second argument to d3.selection#data().
|
||||
coreEntity.key = function(entity) {
|
||||
return entity.id + 'v' + (entity.v || 0);
|
||||
};
|
||||
|
||||
|
||||
coreEntity.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)) {
|
||||
if (source[prop] === undefined) {
|
||||
delete this[prop];
|
||||
} else {
|
||||
this[prop] = source[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.id && this.type) {
|
||||
this.id = coreEntity.id(this.type);
|
||||
}
|
||||
if (!this.hasOwnProperty('visible')) {
|
||||
this.visible = true;
|
||||
}
|
||||
|
||||
if (debug) {
|
||||
Object.freeze(this);
|
||||
Object.freeze(this.tags);
|
||||
|
||||
if (this.loc) Object.freeze(this.loc);
|
||||
if (this.nodes) Object.freeze(this.nodes);
|
||||
if (this.members) Object.freeze(this.members);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
|
||||
copy: function(resolver, copies) {
|
||||
if (copies[this.id])
|
||||
return copies[this.id];
|
||||
|
||||
var copy = coreEntity(this, {id: undefined, user: undefined, version: undefined});
|
||||
copies[this.id] = copy;
|
||||
|
||||
return copy;
|
||||
},
|
||||
|
||||
|
||||
osmId: function() {
|
||||
return coreEntity.id.toOSM(this.id);
|
||||
},
|
||||
|
||||
|
||||
isNew: function() {
|
||||
return this.osmId() < 0;
|
||||
},
|
||||
|
||||
|
||||
update: function(attrs) {
|
||||
return coreEntity(this, attrs, {v: 1 + (this.v || 0)});
|
||||
},
|
||||
|
||||
|
||||
mergeTags: function(tags) {
|
||||
var merged = _.clone(this.tags), changed = false;
|
||||
for (var k in tags) {
|
||||
var t1 = merged[k],
|
||||
t2 = tags[k];
|
||||
if (!t1) {
|
||||
changed = true;
|
||||
merged[k] = t2;
|
||||
} else if (t1 !== t2) {
|
||||
changed = true;
|
||||
merged[k] = _.union(t1.split(/;\s*/), t2.split(/;\s*/)).join(';');
|
||||
}
|
||||
}
|
||||
return changed ? this.update({tags: merged}) : this;
|
||||
},
|
||||
|
||||
|
||||
intersects: function(extent, resolver) {
|
||||
return this.extent(resolver).intersects(extent);
|
||||
},
|
||||
|
||||
|
||||
isUsed: function(resolver) {
|
||||
return _.without(Object.keys(this.tags), 'area').length > 0 ||
|
||||
resolver.parentRelations(this).length > 0;
|
||||
},
|
||||
|
||||
|
||||
hasInterestingTags: function() {
|
||||
return _.keys(this.tags).some(coreInterestingTag);
|
||||
},
|
||||
|
||||
|
||||
isHighwayIntersection: function() {
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
deprecatedTags: function() {
|
||||
var tags = _.toPairs(this.tags);
|
||||
var deprecated = {};
|
||||
|
||||
dataDeprecated.forEach(function(d) {
|
||||
var match = _.toPairs(d.old)[0];
|
||||
tags.forEach(function(t) {
|
||||
if (t[0] === match[0] &&
|
||||
(t[1] === match[1] || match[1] === '*')) {
|
||||
deprecated[t[0]] = t[1];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return deprecated;
|
||||
}
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import _ from 'lodash';
|
||||
import { utilGetPrototypeOf } from '../util/index';
|
||||
import { debug } from '../index';
|
||||
import { utilGetPrototypeOf } from '../util/index';
|
||||
|
||||
|
||||
export function coreGraph(other, mutable) {
|
||||
|
||||
@@ -2,9 +2,9 @@ import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import * as Validations from '../validations/index';
|
||||
import { coreDifference } from './difference';
|
||||
import { coreEntity } from './entity';
|
||||
import { coreGraph } from './graph';
|
||||
import { coreTree } from './tree';
|
||||
import { osmEntity } from '../osm/entity';
|
||||
import { uiLoading } from '../ui/index';
|
||||
import { utilSessionMutex } from '../util/index';
|
||||
import { utilRebind } from '../util/rebind';
|
||||
@@ -244,7 +244,7 @@ export function coreHistory(context) {
|
||||
|
||||
_.forEach(i.graph.entities, function(entity, id) {
|
||||
if (entity) {
|
||||
var key = coreEntity.key(entity);
|
||||
var key = osmEntity.key(entity);
|
||||
allEntities[key] = entity;
|
||||
modified.push(key);
|
||||
} else {
|
||||
@@ -279,7 +279,7 @@ export function coreHistory(context) {
|
||||
entities: _.values(allEntities),
|
||||
baseEntities: _.values(baseEntities),
|
||||
stack: s,
|
||||
nextIDs: coreEntity.id.next,
|
||||
nextIDs: osmEntity.id.next,
|
||||
index: index
|
||||
});
|
||||
},
|
||||
@@ -289,21 +289,21 @@ export function coreHistory(context) {
|
||||
var h = JSON.parse(json),
|
||||
loadComplete = true;
|
||||
|
||||
coreEntity.id.next = h.nextIDs;
|
||||
osmEntity.id.next = h.nextIDs;
|
||||
index = h.index;
|
||||
|
||||
if (h.version === 2 || h.version === 3) {
|
||||
var allEntities = {};
|
||||
|
||||
h.entities.forEach(function(entity) {
|
||||
allEntities[coreEntity.key(entity)] = coreEntity(entity);
|
||||
allEntities[osmEntity.key(entity)] = osmEntity(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 coreEntity(d); });
|
||||
var baseEntities = h.baseEntities.map(function(d) { return osmEntity(d); });
|
||||
stack[0].graph.rebase(baseEntities, _.map(stack, 'graph'), true);
|
||||
tree.rebase(baseEntities, true);
|
||||
|
||||
@@ -382,7 +382,7 @@ export function coreHistory(context) {
|
||||
|
||||
for (var i in d.entities) {
|
||||
var entity = d.entities[i];
|
||||
entities[i] = entity === 'undefined' ? undefined : coreEntity(entity);
|
||||
entities[i] = entity === 'undefined' ? undefined : osmEntity(entity);
|
||||
}
|
||||
|
||||
d.graph = coreGraph(stack[0].graph).load(entities);
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
export { coreConnection } from './connection';
|
||||
export { coreContext } from './context';
|
||||
export { coreDifference } from './difference';
|
||||
export { coreEntity } from './entity';
|
||||
export { coreGraph } from './graph';
|
||||
export { coreHistory } from './history';
|
||||
export { coreNode } from './node';
|
||||
export { coreRelation } from './relation';
|
||||
export { coreOneWayTags, corePavedTags, coreInterestingTag } from './tags';
|
||||
export { coreTree } from './tree';
|
||||
export { coreWay } from './way';
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { coreEntity } from './entity';
|
||||
import { geoExtent } from '../geo/index';
|
||||
|
||||
export function coreNode() {
|
||||
if (!(this instanceof coreNode)) {
|
||||
return (new coreNode()).initialize(arguments);
|
||||
} else if (arguments.length) {
|
||||
this.initialize(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
coreEntity.node = coreNode;
|
||||
|
||||
coreNode.prototype = Object.create(coreEntity.prototype);
|
||||
|
||||
_.extend(coreNode.prototype, {
|
||||
|
||||
type: 'node',
|
||||
|
||||
|
||||
extent: function() {
|
||||
return new geoExtent(this.loc);
|
||||
},
|
||||
|
||||
|
||||
geometry: function(graph) {
|
||||
return graph.transient(this, 'geometry', function() {
|
||||
return graph.isPoi(this) ? 'point' : 'vertex';
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
move: function(loc) {
|
||||
return this.update({loc: loc});
|
||||
},
|
||||
|
||||
|
||||
isIntersection: function(resolver) {
|
||||
return resolver.transient(this, 'isIntersection', function() {
|
||||
return resolver.parentWays(this).filter(function(parent) {
|
||||
return (parent.tags.highway ||
|
||||
parent.tags.waterway ||
|
||||
parent.tags.railway ||
|
||||
parent.tags.aeroway) &&
|
||||
parent.geometry(resolver) === 'line';
|
||||
}).length > 1;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
isHighwayIntersection: function(resolver) {
|
||||
return resolver.transient(this, 'isHighwayIntersection', function() {
|
||||
return resolver.parentWays(this).filter(function(parent) {
|
||||
return parent.tags.highway && parent.geometry(resolver) === 'line';
|
||||
}).length > 1;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
isOnAddressLine: function(resolver) {
|
||||
return resolver.transient(this, 'isOnAddressLine', function() {
|
||||
return resolver.parentWays(this).filter(function(parent) {
|
||||
return parent.tags.hasOwnProperty('addr:interpolation') &&
|
||||
parent.geometry(resolver) === 'line';
|
||||
}).length > 0;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
asJXON: function(changeset_id) {
|
||||
var r = {
|
||||
node: {
|
||||
'@id': this.osmId(),
|
||||
'@lon': this.loc[0],
|
||||
'@lat': this.loc[1],
|
||||
'@version': (this.version || 0),
|
||||
tag: _.map(this.tags, function(v, k) {
|
||||
return { keyAttributes: { k: k, v: v } };
|
||||
})
|
||||
}
|
||||
};
|
||||
if (changeset_id) r.node['@changeset'] = changeset_id;
|
||||
return r;
|
||||
},
|
||||
|
||||
|
||||
asGeoJSON: function() {
|
||||
return {
|
||||
type: 'Point',
|
||||
coordinates: this.loc
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -1,310 +0,0 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { coreEntity } from './entity';
|
||||
import {
|
||||
geoExtent,
|
||||
geoJoinWays,
|
||||
geoPolygonContainsPolygon,
|
||||
geoPolygonIntersectsPolygon
|
||||
} from '../geo/index';
|
||||
|
||||
|
||||
export function coreRelation() {
|
||||
if (!(this instanceof coreRelation)) {
|
||||
return (new coreRelation()).initialize(arguments);
|
||||
} else if (arguments.length) {
|
||||
this.initialize(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
coreEntity.relation = coreRelation;
|
||||
|
||||
coreRelation.prototype = Object.create(coreEntity.prototype);
|
||||
|
||||
|
||||
coreRelation.creationOrder = function(a, b) {
|
||||
var aId = parseInt(coreEntity.id.toOSM(a.id), 10);
|
||||
var bId = parseInt(coreEntity.id.toOSM(b.id), 10);
|
||||
|
||||
if (aId < 0 || bId < 0) return aId - bId;
|
||||
return bId - aId;
|
||||
};
|
||||
|
||||
|
||||
_.extend(coreRelation.prototype, {
|
||||
type: 'relation',
|
||||
members: [],
|
||||
|
||||
|
||||
copy: function(resolver, copies) {
|
||||
if (copies[this.id])
|
||||
return copies[this.id];
|
||||
|
||||
var copy = coreEntity.prototype.copy.call(this, resolver, copies);
|
||||
|
||||
var members = this.members.map(function(member) {
|
||||
return _.extend({}, member, { id: resolver.entity(member.id).copy(resolver, copies).id });
|
||||
});
|
||||
|
||||
copy = copy.update({members: members});
|
||||
copies[this.id] = copy;
|
||||
|
||||
return copy;
|
||||
},
|
||||
|
||||
|
||||
extent: function(resolver, memo) {
|
||||
return resolver.transient(this, 'extent', function() {
|
||||
if (memo && memo[this.id]) return geoExtent();
|
||||
memo = memo || {};
|
||||
memo[this.id] = true;
|
||||
|
||||
var extent = geoExtent();
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
var member = resolver.hasEntity(this.members[i].id);
|
||||
if (member) {
|
||||
extent._extend(member.extent(resolver, memo));
|
||||
}
|
||||
}
|
||||
return extent;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
geometry: function(graph) {
|
||||
return graph.transient(this, 'geometry', function() {
|
||||
return this.isMultipolygon() ? 'area' : 'relation';
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
isDegenerate: function() {
|
||||
return this.members.length === 0;
|
||||
},
|
||||
|
||||
|
||||
// Return an array of members, each extended with an 'index' property whose value
|
||||
// is the member index.
|
||||
indexedMembers: function() {
|
||||
var result = new Array(this.members.length);
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
result[i] = _.extend({}, this.members[i], {index: i});
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
// Return the first member with the given role. A copy of the member object
|
||||
// is returned, extended with an 'index' property whose value is the member index.
|
||||
memberByRole: function(role) {
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
if (this.members[i].role === role) {
|
||||
return _.extend({}, this.members[i], {index: i});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Return the first member with the given id. A copy of the member object
|
||||
// is returned, extended with an 'index' property whose value is the member index.
|
||||
memberById: function(id) {
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
if (this.members[i].id === id) {
|
||||
return _.extend({}, this.members[i], {index: i});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
// Return the first member with the given id and role. A copy of the member object
|
||||
// is returned, extended with an 'index' property whose value is the member index.
|
||||
memberByIdAndRole: function(id, role) {
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
if (this.members[i].id === id && this.members[i].role === role) {
|
||||
return _.extend({}, this.members[i], {index: i});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
addMember: function(member, index) {
|
||||
var members = this.members.slice();
|
||||
members.splice(index === undefined ? members.length : index, 0, member);
|
||||
return this.update({members: members});
|
||||
},
|
||||
|
||||
|
||||
updateMember: function(member, index) {
|
||||
var members = this.members.slice();
|
||||
members.splice(index, 1, _.extend({}, members[index], member));
|
||||
return this.update({members: members});
|
||||
},
|
||||
|
||||
|
||||
removeMember: function(index) {
|
||||
var members = this.members.slice();
|
||||
members.splice(index, 1);
|
||||
return this.update({members: members});
|
||||
},
|
||||
|
||||
|
||||
removeMembersWithID: function(id) {
|
||||
var members = _.reject(this.members, function(m) { return m.id === id; });
|
||||
return this.update({members: members});
|
||||
},
|
||||
|
||||
|
||||
// Wherever a member appears with id `needle.id`, replace it with a member
|
||||
// with id `replacement.id`, type `replacement.type`, and the original role,
|
||||
// unless a member already exists with that id and role. Return an updated
|
||||
// relation.
|
||||
replaceMember: function(needle, replacement) {
|
||||
if (!this.memberById(needle.id))
|
||||
return this;
|
||||
|
||||
var members = [];
|
||||
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
var member = this.members[i];
|
||||
if (member.id !== needle.id) {
|
||||
members.push(member);
|
||||
} else if (!this.memberByIdAndRole(replacement.id, member.role)) {
|
||||
members.push({id: replacement.id, type: replacement.type, role: member.role});
|
||||
}
|
||||
}
|
||||
|
||||
return this.update({members: members});
|
||||
},
|
||||
|
||||
|
||||
asJXON: function(changeset_id) {
|
||||
var r = {
|
||||
relation: {
|
||||
'@id': this.osmId(),
|
||||
'@version': this.version || 0,
|
||||
member: _.map(this.members, function(member) {
|
||||
return {
|
||||
keyAttributes: {
|
||||
type: member.type,
|
||||
role: member.role,
|
||||
ref: coreEntity.id.toOSM(member.id)
|
||||
}
|
||||
};
|
||||
}),
|
||||
tag: _.map(this.tags, function(v, k) {
|
||||
return { keyAttributes: { k: k, v: v } };
|
||||
})
|
||||
}
|
||||
};
|
||||
if (changeset_id) r.relation['@changeset'] = changeset_id;
|
||||
return r;
|
||||
},
|
||||
|
||||
|
||||
asGeoJSON: function(resolver) {
|
||||
return resolver.transient(this, 'GeoJSON', function () {
|
||||
if (this.isMultipolygon()) {
|
||||
return {
|
||||
type: 'MultiPolygon',
|
||||
coordinates: this.multipolygon(resolver)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'FeatureCollection',
|
||||
properties: this.tags,
|
||||
features: this.members.map(function (member) {
|
||||
return _.extend({role: member.role}, resolver.entity(member.id).asGeoJSON(resolver));
|
||||
})
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
area: function(resolver) {
|
||||
return resolver.transient(this, 'area', function() {
|
||||
return d3.geoArea(this.asGeoJSON(resolver));
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
isMultipolygon: function() {
|
||||
return this.tags.type === 'multipolygon';
|
||||
},
|
||||
|
||||
|
||||
isComplete: function(resolver) {
|
||||
for (var i = 0; i < this.members.length; i++) {
|
||||
if (!resolver.hasEntity(this.members[i].id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
isRestriction: function() {
|
||||
return !!(this.tags.type && this.tags.type.match(/^restriction:?/));
|
||||
},
|
||||
|
||||
|
||||
// Returns an array [A0, ... An], each Ai being an array of node arrays [Nds0, ... Ndsm],
|
||||
// where Nds0 is an outer ring and subsequent Ndsi's (if any i > 0) being inner rings.
|
||||
//
|
||||
// This corresponds to the structure needed for rendering a multipolygon path using a
|
||||
// `evenodd` fill rule, as well as the structure of a GeoJSON MultiPolygon geometry.
|
||||
//
|
||||
// In the case of invalid geometries, this function will still return a result which
|
||||
// includes the nodes of all way members, but some Nds may be unclosed and some inner
|
||||
// rings not matched with the intended outer ring.
|
||||
//
|
||||
multipolygon: function(resolver) {
|
||||
var outers = this.members.filter(function(m) { return 'outer' === (m.role || 'outer'); }),
|
||||
inners = this.members.filter(function(m) { return 'inner' === m.role; });
|
||||
|
||||
outers = geoJoinWays(outers, resolver);
|
||||
inners = geoJoinWays(inners, resolver);
|
||||
|
||||
outers = outers.map(function(outer) { return _.map(outer.nodes, 'loc'); });
|
||||
inners = inners.map(function(inner) { return _.map(inner.nodes, 'loc'); });
|
||||
|
||||
var result = outers.map(function(o) {
|
||||
// Heuristic for detecting counterclockwise winding order. Assumes
|
||||
// that OpenStreetMap polygons are not hemisphere-spanning.
|
||||
return [d3.geoArea({ type: 'Polygon', coordinates: [o] }) > 2 * Math.PI ? o.reverse() : o];
|
||||
});
|
||||
|
||||
function findOuter(inner) {
|
||||
var o, outer;
|
||||
|
||||
for (o = 0; o < outers.length; o++) {
|
||||
outer = outers[o];
|
||||
if (geoPolygonContainsPolygon(outer, inner))
|
||||
return o;
|
||||
}
|
||||
|
||||
for (o = 0; o < outers.length; o++) {
|
||||
outer = outers[o];
|
||||
if (geoPolygonIntersectsPolygon(outer, inner))
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < inners.length; i++) {
|
||||
var inner = inners[i];
|
||||
|
||||
if (d3.geoArea({ type: 'Polygon', coordinates: [inner] }) < 2 * Math.PI) {
|
||||
inner = inner.reverse();
|
||||
}
|
||||
|
||||
var o = findOuter(inners[i]);
|
||||
if (o !== undefined)
|
||||
result[o].push(inners[i]);
|
||||
else
|
||||
result.push([inners[i]]); // Invalid geometry
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
@@ -1,53 +0,0 @@
|
||||
export function coreInterestingTag(key) {
|
||||
return key !== 'attribution' &&
|
||||
key !== 'created_by' &&
|
||||
key !== 'source' &&
|
||||
key !== 'odbl' &&
|
||||
key.indexOf('tiger:') !== 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
export var coreOneWayTags = {
|
||||
'aerialway': {
|
||||
'chair_lift': true,
|
||||
'mixed_lift': true,
|
||||
't-bar': true,
|
||||
'j-bar': true,
|
||||
'platter': true,
|
||||
'rope_tow': true,
|
||||
'magic_carpet': true,
|
||||
'yes': true
|
||||
},
|
||||
'highway': {
|
||||
'motorway': true,
|
||||
'motorway_link': true
|
||||
},
|
||||
'junction': {
|
||||
'roundabout': true
|
||||
},
|
||||
'man_made': {
|
||||
'piste:halfpipe': true
|
||||
},
|
||||
'piste:type': {
|
||||
'downhill': true,
|
||||
'sled': true,
|
||||
'yes': true
|
||||
},
|
||||
'waterway': {
|
||||
'river': true,
|
||||
'stream': true
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export var corePavedTags = {
|
||||
'surface': {
|
||||
'paved': true,
|
||||
'asphalt': true,
|
||||
'concrete': true
|
||||
},
|
||||
'tracktype': {
|
||||
'grade1': true
|
||||
}
|
||||
};
|
||||
@@ -1,524 +0,0 @@
|
||||
import * as d3 from 'd3';
|
||||
import _ from 'lodash';
|
||||
import { geoExtent, geoCross } from '../geo/index';
|
||||
import { coreEntity } from './entity';
|
||||
import { coreOneWayTags } from './tags';
|
||||
import { areaKeys } from './context';
|
||||
|
||||
|
||||
export function coreWay() {
|
||||
if (!(this instanceof coreWay)) {
|
||||
return (new coreWay()).initialize(arguments);
|
||||
} else if (arguments.length) {
|
||||
this.initialize(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
coreEntity.way = coreWay;
|
||||
|
||||
coreWay.prototype = Object.create(coreEntity.prototype);
|
||||
|
||||
|
||||
_.extend(coreWay.prototype, {
|
||||
type: 'way',
|
||||
nodes: [],
|
||||
|
||||
|
||||
copy: function(resolver, copies) {
|
||||
if (copies[this.id])
|
||||
return copies[this.id];
|
||||
|
||||
var copy = coreEntity.prototype.copy.call(this, resolver, copies);
|
||||
|
||||
var nodes = this.nodes.map(function(id) {
|
||||
return resolver.entity(id).copy(resolver, copies).id;
|
||||
});
|
||||
|
||||
copy = copy.update({ nodes: nodes });
|
||||
copies[this.id] = copy;
|
||||
|
||||
return copy;
|
||||
},
|
||||
|
||||
|
||||
extent: function(resolver) {
|
||||
return resolver.transient(this, 'extent', function() {
|
||||
var extent = geoExtent();
|
||||
for (var i = 0; i < this.nodes.length; i++) {
|
||||
var node = resolver.hasEntity(this.nodes[i]);
|
||||
if (node) {
|
||||
extent._extend(node.extent());
|
||||
}
|
||||
}
|
||||
return extent;
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
first: function() {
|
||||
return this.nodes[0];
|
||||
},
|
||||
|
||||
|
||||
last: function() {
|
||||
return this.nodes[this.nodes.length - 1];
|
||||
},
|
||||
|
||||
|
||||
contains: function(node) {
|
||||
return this.nodes.indexOf(node) >= 0;
|
||||
},
|
||||
|
||||
|
||||
affix: function(node) {
|
||||
if (this.nodes[0] === node) return 'prefix';
|
||||
if (this.nodes[this.nodes.length - 1] === node) return 'suffix';
|
||||
},
|
||||
|
||||
|
||||
layer: function() {
|
||||
// explicit layer tag, clamp between -10, 10..
|
||||
if (isFinite(this.tags.layer)) {
|
||||
return Math.max(-10, Math.min(+(this.tags.layer), 10));
|
||||
}
|
||||
|
||||
// implied layer tag..
|
||||
if (this.tags.location === 'overground') return 1;
|
||||
if (this.tags.location === 'underground') return -1;
|
||||
if (this.tags.location === 'underwater') return -10;
|
||||
|
||||
if (this.tags.power === 'line') return 10;
|
||||
if (this.tags.power === 'minor_line') return 10;
|
||||
if (this.tags.aerialway) return 10;
|
||||
if (this.tags.bridge) return 1;
|
||||
if (this.tags.cutting) return -1;
|
||||
if (this.tags.tunnel) return -1;
|
||||
if (this.tags.waterway) return -1;
|
||||
if (this.tags.man_made === 'pipeline') return -10;
|
||||
if (this.tags.boundary) return -10;
|
||||
return 0;
|
||||
},
|
||||
|
||||
|
||||
isOneWay: function() {
|
||||
// explicit oneway tag..
|
||||
if (['yes', '1', '-1'].indexOf(this.tags.oneway) !== -1) { return true; }
|
||||
if (['no', '0'].indexOf(this.tags.oneway) !== -1) { return false; }
|
||||
|
||||
// implied oneway tag..
|
||||
for (var key in this.tags) {
|
||||
if (key in coreOneWayTags && (this.tags[key] in coreOneWayTags[key]))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
lanes: function() {
|
||||
function getLaneCount() {
|
||||
var count;
|
||||
// fill laneCount with defaults
|
||||
switch (tags.highway) {
|
||||
case 'trunk':
|
||||
case 'motorway':
|
||||
count = oneway ? 2 : 4;
|
||||
break;
|
||||
default:
|
||||
count = oneway ? 1 : 2;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tags.lanes) count = parseInt(tags.lanes, 10);
|
||||
return count;
|
||||
}
|
||||
|
||||
function parseMaxspeed() {
|
||||
var maxspeed = tags.maxspeed;
|
||||
if (_.isNumber(maxspeed)) return maxspeed;
|
||||
if (_.isString(maxspeed)) {
|
||||
maxspeed = maxspeed.match(/^([0-9][\.0-9]+?)(?:[ ]?(?:km\/h|kmh|kph|mph|knots))?$/g);
|
||||
if (!maxspeed) return;
|
||||
return parseInt(maxspeed, 10);
|
||||
}
|
||||
}
|
||||
|
||||
function parseLaneDirections() {
|
||||
var forward = parseInt(tags['lanes:forward'], 10);
|
||||
var backward = parseInt(tags['lanes:backward'], 10);
|
||||
var bothways = parseInt(tags['lanes:both_ways'], 10) > 0 ? 1 : 0;
|
||||
|
||||
if (parseInt(tags.oneway, 10) === -1) {
|
||||
forward = 0;
|
||||
bothways = 0;
|
||||
backward = laneCount;
|
||||
}
|
||||
else if (oneway) {
|
||||
forward = laneCount;
|
||||
bothways = 0;
|
||||
backward = 0;
|
||||
}
|
||||
else if (_.isNaN(forward) && _.isNaN(backward)) {
|
||||
backward = Math.floor((laneCount - bothways) / 2);
|
||||
forward = laneCount - bothways - backward;
|
||||
}
|
||||
else if (_.isNaN(forward)) {
|
||||
if (backward > laneCount - bothways) {
|
||||
backward = laneCount - bothways;
|
||||
}
|
||||
forward = laneCount - bothways - backward;
|
||||
}
|
||||
else if (_.isNaN(backward)) {
|
||||
if (forward > laneCount - bothways) {
|
||||
forward = laneCount - bothways;
|
||||
}
|
||||
backward = laneCount - bothways - forward;
|
||||
}
|
||||
return {
|
||||
forward: forward,
|
||||
backward: backward,
|
||||
bothways: bothways
|
||||
};
|
||||
}
|
||||
|
||||
function parseTurnLanes(key){
|
||||
var validValues = [
|
||||
'left', 'slight_left', 'sharp_left', 'through', 'right', 'slight_right',
|
||||
'sharp_right', 'reverse', 'merge_to_left', 'merge_to_right', 'none'
|
||||
];
|
||||
var str = tags[key];
|
||||
if (!str) return;
|
||||
var parsedArray = str.split('|')
|
||||
.map(function (s) {
|
||||
if (s === '') s = 'none';
|
||||
return s.split(';')
|
||||
.map(function (d) {
|
||||
return validValues.indexOf(d) === -1 ? 'unknown': d;
|
||||
});
|
||||
});
|
||||
return parsedArray;
|
||||
}
|
||||
|
||||
function parseMaxspeedLanes(key) {
|
||||
var str = tags[key];
|
||||
if (!str) return;
|
||||
var parsedArray = str.split('|')
|
||||
.map(function (s) {
|
||||
if (s === 'none') return s;
|
||||
var m = parseInt(s, 10);
|
||||
if (s === '' || m === maxspeed) return null;
|
||||
return _.isNaN(m) ? 'unknown': m;
|
||||
});
|
||||
return parsedArray;
|
||||
}
|
||||
|
||||
function parseMiscLanes(key) {
|
||||
var validValues = [
|
||||
'yes', 'no', 'designated'
|
||||
];
|
||||
var str = tags[key];
|
||||
if (!str) return;
|
||||
var parsedArray = str.split('|')
|
||||
.map(function (s) {
|
||||
if (s === '') s = 'no';
|
||||
return validValues.indexOf(s) === -1 ? 'unknown': s;
|
||||
});
|
||||
return parsedArray;
|
||||
}
|
||||
|
||||
function parseBicycleWay(key) {
|
||||
var validValues = [
|
||||
'yes', 'no', 'designated', 'lane'
|
||||
];
|
||||
var str = tags[key];
|
||||
if (!str) return;
|
||||
var parsedArray = str.split('|')
|
||||
.map(function (s) {
|
||||
if (s === '') s = 'no';
|
||||
return validValues.indexOf(s) === -1 ? 'unknown': s;
|
||||
});
|
||||
return parsedArray;
|
||||
}
|
||||
|
||||
function mapToLanesObj(data, key) {
|
||||
if (data.forward) data.forward.forEach(function(l, i) {
|
||||
if (!lanesObj.forward[i]) lanesObj.forward[i] = {};
|
||||
lanesObj.forward[i][key] = l;
|
||||
});
|
||||
if (data.backward) data.backward.forEach(function(l, i) {
|
||||
if (!lanesObj.backward[i]) lanesObj.backward[i] = {};
|
||||
lanesObj.backward[i][key] = l;
|
||||
});
|
||||
if (data.unspecified) data.unspecified.forEach(function(l, i) {
|
||||
if (!lanesObj.unspecified[i]) lanesObj.unspecified[i] = {};
|
||||
lanesObj.unspecified[i][key] = l;
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.tags.highway) return null;
|
||||
|
||||
var tags = this.tags;
|
||||
var oneway = this.isOneWay();
|
||||
var laneCount = getLaneCount();
|
||||
var maxspeed = parseMaxspeed();
|
||||
|
||||
var laneDirections = parseLaneDirections();
|
||||
var forward = laneDirections.forward;
|
||||
var backward = laneDirections.backward;
|
||||
var bothways = laneDirections.bothways;
|
||||
|
||||
// parse the piped string 'x|y|z' format
|
||||
var turnLanes = {};
|
||||
turnLanes.unspecified = parseTurnLanes('turn:lanes');
|
||||
turnLanes.forward = parseTurnLanes('turn:lanes:forward');
|
||||
turnLanes.backward = parseTurnLanes('turn:lanes:backward');
|
||||
|
||||
var maxspeedLanes = {};
|
||||
maxspeedLanes.unspecified = parseMaxspeedLanes('maxspeed:lanes');
|
||||
maxspeedLanes.forward = parseMaxspeedLanes('maxspeed:lanes:forward');
|
||||
maxspeedLanes.backward = parseMaxspeedLanes('maxspeed:lanes:backward');
|
||||
|
||||
var psvLanes = {};
|
||||
psvLanes.unspecified = parseMiscLanes('psv:lanes');
|
||||
psvLanes.forward = parseMiscLanes('psv:lanes:forward');
|
||||
psvLanes.backward = parseMiscLanes('psv:lanes:backward');
|
||||
|
||||
var busLanes = {};
|
||||
busLanes.unspecified = parseMiscLanes('bus:lanes');
|
||||
busLanes.forward = parseMiscLanes('bus:lanes:forward');
|
||||
busLanes.backward = parseMiscLanes('bus:lanes:backward');
|
||||
|
||||
var taxiLanes = {};
|
||||
taxiLanes.unspecified = parseMiscLanes('taxi:lanes');
|
||||
taxiLanes.forward = parseMiscLanes('taxi:lanes:forward');
|
||||
taxiLanes.backward = parseMiscLanes('taxi:lanes:backward');
|
||||
|
||||
var hovLanes = {};
|
||||
hovLanes.unspecified = parseMiscLanes('hov:lanes');
|
||||
hovLanes.forward = parseMiscLanes('hov:lanes:forward');
|
||||
hovLanes.backward = parseMiscLanes('hov:lanes:backward');
|
||||
|
||||
var hgvLanes = {};
|
||||
hgvLanes.unspecified = parseMiscLanes('hgv:lanes');
|
||||
hgvLanes.forward = parseMiscLanes('hgv:lanes:forward');
|
||||
hgvLanes.backward = parseMiscLanes('hgv:lanes:backward');
|
||||
|
||||
var bicyclewayLanes = {};
|
||||
bicyclewayLanes.unspecified = parseBicycleWay('bicycleway:lanes');
|
||||
bicyclewayLanes.forward = parseBicycleWay('bicycleway:lanes:forward');
|
||||
bicyclewayLanes.backward = parseBicycleWay('bicycleway:lanes:backward');
|
||||
|
||||
var lanesObj = {
|
||||
forward: [],
|
||||
backward: [],
|
||||
unspecified: []
|
||||
};
|
||||
|
||||
// map forward/backward/unspecified of each lane type to lanesObj
|
||||
mapToLanesObj(turnLanes, 'turnLane');
|
||||
mapToLanesObj(maxspeedLanes, 'maxspeed');
|
||||
mapToLanesObj(psvLanes, 'psv');
|
||||
mapToLanesObj(busLanes, 'bus');
|
||||
mapToLanesObj(taxiLanes, 'taxi');
|
||||
mapToLanesObj(hovLanes, 'hov');
|
||||
mapToLanesObj(hgvLanes, 'hgv');
|
||||
mapToLanesObj(bicyclewayLanes, 'bicycleway');
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
count: laneCount,
|
||||
oneway: oneway,
|
||||
forward: forward,
|
||||
backward: backward,
|
||||
bothways: bothways,
|
||||
turnLanes: turnLanes,
|
||||
maxspeed: maxspeed,
|
||||
maxspeedLanes: maxspeedLanes,
|
||||
psvLanes: psvLanes,
|
||||
busLanes: busLanes,
|
||||
taxiLanes: taxiLanes,
|
||||
hovLanes: hovLanes,
|
||||
hgvLanes: hgvLanes,
|
||||
bicyclewayLanes: bicyclewayLanes
|
||||
},
|
||||
lanes: lanesObj
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
isClosed: function() {
|
||||
return this.nodes.length > 0 && this.first() === this.last();
|
||||
},
|
||||
|
||||
|
||||
isConvex: function(resolver) {
|
||||
if (!this.isClosed() || this.isDegenerate()) return null;
|
||||
|
||||
var nodes = _.uniq(resolver.childNodes(this)),
|
||||
coords = _.map(nodes, 'loc'),
|
||||
curr = 0, prev = 0;
|
||||
|
||||
for (var i = 0; i < coords.length; i++) {
|
||||
var o = coords[(i+1) % coords.length],
|
||||
a = coords[i],
|
||||
b = coords[(i+2) % coords.length],
|
||||
res = geoCross(o, a, b);
|
||||
|
||||
curr = (res > 0) ? 1 : (res < 0) ? -1 : 0;
|
||||
if (curr === 0) {
|
||||
continue;
|
||||
} else if (prev && curr !== prev) {
|
||||
return false;
|
||||
}
|
||||
prev = curr;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
|
||||
isArea: function() {
|
||||
if (this.tags.area === 'yes')
|
||||
return true;
|
||||
if (!this.isClosed() || this.tags.area === 'no')
|
||||
return false;
|
||||
for (var key in this.tags) {
|
||||
if (key in areaKeys && !(this.tags[key] in areaKeys[key])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
isDegenerate: function() {
|
||||
return _.uniq(this.nodes).length < (this.isArea() ? 3 : 2);
|
||||
},
|
||||
|
||||
|
||||
areAdjacent: function(n1, n2) {
|
||||
for (var i = 0; i < this.nodes.length; i++) {
|
||||
if (this.nodes[i] === n1) {
|
||||
if (this.nodes[i - 1] === n2) return true;
|
||||
if (this.nodes[i + 1] === n2) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
|
||||
geometry: function(graph) {
|
||||
return graph.transient(this, 'geometry', function() {
|
||||
return this.isArea() ? 'area' : 'line';
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
addNode: function(id, index) {
|
||||
var nodes = this.nodes.slice();
|
||||
nodes.splice(index === undefined ? nodes.length : index, 0, id);
|
||||
return this.update({nodes: nodes});
|
||||
},
|
||||
|
||||
|
||||
updateNode: function(id, index) {
|
||||
var nodes = this.nodes.slice();
|
||||
nodes.splice(index, 1, id);
|
||||
return this.update({nodes: nodes});
|
||||
},
|
||||
|
||||
|
||||
replaceNode: function(needle, replacement) {
|
||||
if (this.nodes.indexOf(needle) < 0)
|
||||
return this;
|
||||
|
||||
var nodes = this.nodes.slice();
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (nodes[i] === needle) {
|
||||
nodes[i] = replacement;
|
||||
}
|
||||
}
|
||||
return this.update({nodes: nodes});
|
||||
},
|
||||
|
||||
|
||||
removeNode: function(id) {
|
||||
var nodes = [];
|
||||
|
||||
for (var i = 0; i < this.nodes.length; i++) {
|
||||
var node = this.nodes[i];
|
||||
if (node !== id && nodes[nodes.length - 1] !== node) {
|
||||
nodes.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve circularity
|
||||
if (this.nodes.length > 1 && this.first() === id && this.last() === id && nodes[nodes.length - 1] !== nodes[0]) {
|
||||
nodes.push(nodes[0]);
|
||||
}
|
||||
|
||||
return this.update({nodes: nodes});
|
||||
},
|
||||
|
||||
|
||||
asJXON: function(changeset_id) {
|
||||
var r = {
|
||||
way: {
|
||||
'@id': this.osmId(),
|
||||
'@version': this.version || 0,
|
||||
nd: _.map(this.nodes, function(id) {
|
||||
return { keyAttributes: { ref: coreEntity.id.toOSM(id) } };
|
||||
}),
|
||||
tag: _.map(this.tags, function(v, k) {
|
||||
return { keyAttributes: { k: k, v: v } };
|
||||
})
|
||||
}
|
||||
};
|
||||
if (changeset_id) r.way['@changeset'] = changeset_id;
|
||||
return r;
|
||||
},
|
||||
|
||||
|
||||
asGeoJSON: function(resolver) {
|
||||
return resolver.transient(this, 'GeoJSON', function() {
|
||||
var coordinates = _.map(resolver.childNodes(this), 'loc');
|
||||
if (this.isArea() && this.isClosed()) {
|
||||
return {
|
||||
type: 'Polygon',
|
||||
coordinates: [coordinates]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: 'LineString',
|
||||
coordinates: coordinates
|
||||
};
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
||||
area: function(resolver) {
|
||||
return resolver.transient(this, 'area', function() {
|
||||
var nodes = resolver.childNodes(this);
|
||||
|
||||
var json = {
|
||||
type: 'Polygon',
|
||||
coordinates: [_.map(nodes, 'loc')]
|
||||
};
|
||||
|
||||
if (!this.isClosed() && nodes.length) {
|
||||
json.coordinates[0].push(nodes[0].loc);
|
||||
}
|
||||
|
||||
var area = d3.geoArea(json);
|
||||
|
||||
// Heuristic for detecting counterclockwise winding order. Assumes
|
||||
// that OpenStreetMap polygons are not hemisphere-spanning.
|
||||
if (area > 2 * Math.PI) {
|
||||
json.coordinates[0] = json.coordinates[0].reverse();
|
||||
area = d3.geoArea(json);
|
||||
}
|
||||
|
||||
return isNaN(area) ? 0 : area;
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user