modules/osm/entity.js (186 lines of code) (raw):

import { utilArrayUnion, utilUnicodeCharsTruncated } from '@id-sdk/util'; import { debug } from '../index'; import { osmIsInterestingTag } from './tags'; export function osmEntity(attrs) { // For prototypal inheritance. if (this instanceof osmEntity) return; // Create the appropriate subtype. if (attrs && attrs.type) { return osmEntity[attrs.type].apply(this, arguments); } else if (attrs && attrs.id) { return osmEntity[osmEntity.id.type(attrs.id)].apply(this, arguments); } // Initialize a generic Entity (used only in tests). return (new osmEntity()).initialize(arguments); } osmEntity.id = function(type) { return osmEntity.id.fromOSM(type, osmEntity.id.next[type]--); }; osmEntity.id.next = { changeset: -1, node: -1, way: -1, relation: -1 }; osmEntity.id.fromOSM = function(type, id) { return type[0] + id; }; osmEntity.id.toOSM = function(id) { return id.slice(1); }; osmEntity.id.type = function(id) { return { 'c': 'changeset', 'n': 'node', 'w': 'way', 'r': 'relation' }[id[0]]; }; // A function suitable for use as the second argument to d3.selection#data(). osmEntity.key = function(entity) { return entity.id + 'v' + (entity.v || 0); }; var _deprecatedTagValuesByKey; osmEntity.deprecatedTagValuesByKey = function(dataDeprecated) { if (!_deprecatedTagValuesByKey) { _deprecatedTagValuesByKey = {}; dataDeprecated.forEach(function(d) { var oldKeys = Object.keys(d.old); if (oldKeys.length === 1) { var oldKey = oldKeys[0]; var oldValue = d.old[oldKey]; if (oldValue !== '*') { if (!_deprecatedTagValuesByKey[oldKey]) { _deprecatedTagValuesByKey[oldKey] = [oldValue]; } else { _deprecatedTagValuesByKey[oldKey].push(oldValue); } } } }); } return _deprecatedTagValuesByKey; }; osmEntity.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 = osmEntity.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 = osmEntity(this, { id: undefined, user: undefined, version: undefined }); copies[this.id] = copy; return copy; }, osmId: function() { return osmEntity.id.toOSM(this.id); }, isNew: function() { return this.osmId() < 0; }, update: function(attrs) { return osmEntity(this, attrs, { v: 1 + (this.v || 0) }); }, mergeTags: function(tags) { var merged = Object.assign({}, this.tags); // shallow copy var changed = false; for (var k in tags) { var t1 = merged[k]; var t2 = tags[k]; if (!t1) { changed = true; merged[k] = t2; } else if (k === 'building') { if (t2 === 'yes') { continue; } else if (t1 === 'yes') { changed = true; merged[k] = t2; } } else if (t1 !== t2) { changed = true; merged[k] = utilUnicodeCharsTruncated( utilArrayUnion(t1.split(/;\s*/), t2.split(/;\s*/)).join(';'), 255 // avoid exceeding character limit; see also services/osm.js -> maxCharsForTagValue() ); } } return changed ? this.update({ tags: merged }) : this; }, intersects: function(extent, resolver) { return this.extent(resolver).intersects(extent); }, hasNonGeometryTags: function() { return Object.keys(this.tags).some(function(k) { return k !== 'area'; }); }, hasParentRelations: function(resolver) { return resolver.parentRelations(this).length > 0; }, hasInterestingTags: function() { return Object.keys(this.tags).some(osmIsInterestingTag); }, isHighwayIntersection: function() { return false; }, isDegenerate: function() { return true; }, deprecatedTags: function(dataDeprecated) { var tags = this.tags; // if there are no tags, none can be deprecated if (Object.keys(tags).length === 0) return []; var deprecated = []; dataDeprecated.forEach(function(d) { var oldKeys = Object.keys(d.old); if (d.replace) { var hasExistingValues = Object.keys(d.replace).some(function(replaceKey) { if (!tags[replaceKey] || d.old[replaceKey]) return false; var replaceValue = d.replace[replaceKey]; if (replaceValue === '*') return false; if (replaceValue === tags[replaceKey]) return false; return true; }); // don't flag deprecated tags if the upgrade path would overwrite existing data - #7843 if (hasExistingValues) return; } var matchesDeprecatedTags = oldKeys.every(function(oldKey) { if (!tags[oldKey]) return false; if (d.old[oldKey] === '*') return true; if (d.old[oldKey] === tags[oldKey]) return true; var vals = tags[oldKey].split(';').filter(Boolean); if (vals.length === 0) { return false; } else if (vals.length > 1) { return vals.indexOf(d.old[oldKey]) !== -1; } else { if (tags[oldKey] === d.old[oldKey]) { if (d.replace && d.old[oldKey] === d.replace[oldKey]) { var replaceKeys = Object.keys(d.replace); return !replaceKeys.every(function(replaceKey) { return tags[replaceKey] === d.replace[replaceKey]; }); } else { return true; } } } return false; }); if (matchesDeprecatedTags) { deprecated.push(d); } }); return deprecated; } };