/** * @module josm/builder/relation * @example * import {RelationBuilder} from 'josm/builder' * // creates a new relation with no tags and a * // local id * const relation = RelationBuilder.create() */ /* global Java */ // -- imports const Node = Java.type('org.openstreetmap.josm.data.osm.Node') const Way = Java.type('org.openstreetmap.josm.data.osm.Way') const Relation = Java.type('org.openstreetmap.josm.data.osm.Relation') const RelationMember = Java.type('org.openstreetmap.josm.data.osm.RelationMember') const DataSet = Java.type('org.openstreetmap.josm.data.osm.DataSet') const OsmPrimitive = Java.type('org.openstreetmap.josm.data.osm.OsmPrimitive') const LatLon = Java.type('org.openstreetmap.josm.data.coor.LatLon') const List = Java.type('java.util.List') import * as util from 'josm/util' import { assertGlobalId, rememberId, rememberTags, assignTags, rememberIdFromObject, rememberVersionFromObject, checkLat, checkLon, rememberPosFromObject, rememberTagsFromObject } from './common' function receiver (that) { return typeof that === 'object' ? that : new RelationBuilder() } /** * RelationBuilder helps to create OSM * {@class org.openstreetmap.josm.data.osm.Relation}s. * * Methods of RelationBuilder can be used in a static and in an instance * context. * It isn't necessary to create an instance of RelationBuilder, unless it is * configured with a {@class org.openstreetmap.josm.data.osm.DataSet} to * which created ways are added. * @example * import {RelationBuilder} from 'josm/builder' * const DataSet = Java.type('org.openstreetmap.josm.data.osm.DataSet') * * const ds = new DataSet() * // create a relation builder without and underlying dataset ... * let rbuilder = new RelationBuilder() * // ... with an underlying dataset ... * rbuilder = new RelationBuilder(ds) * // ... or using this factory method * rbuilder = RelationBuilder.forDataSet(ds) * * // create a new local relation * const r1 = rbuilder.create() * * // create a new global way * const r2 = rbuilder.withTags({route: 'bicycle'}).create(1111) * * // create a new proxy for a global relation * // (an 'incomplete' node in JOSM terminology) * const r3 = rbuilder.createProxy(2222) * * @class * @param {org.openstreetmap.josm.data.osm.DataSet} ds (optional) a JOSM * dataset which created ways are added to. If missing, the created ways * aren't added to a dataset. * @name RelationBuilder * @summary Helps to create {@class org.openstreetmap.josm.data.osm.Relation}s */ export function RelationBuilder(ds) { if (util.isSomething(ds)) { util.assert(ds instanceof DataSet, 'Expected a DataSet, got {0}', ds) this.ds = ds } this.members = [] } /** * Creates or configures a RelationBuilder which will add created nodes * to the dataset <code>ds</code>. * * @example * import {RelationBuilder} = 'josm/builder' * * // create a new relation builder building to a data set * const DataSet = Java.type('org.openstreetmap.josm.data.osm.DataSet') * const ds = new DataSet() * const rb1 = RelationBuilder.forDataSet(ds) * * // configure an existing relation builder * let rb2 = new RelationBuilder() * rb2 = rb2.forDataSet(ds) * * @return {module:josm/builder/relation~RelationBuilder} the relation builder * @param {org.openstreetmap.josm.data.osm.DataSet} ds a JOSM * dataset which created ways are added to. * @memberof module:josm/builder/relation~RelationBuilder */ function forDataSet (ds) { const builder = receiver(this) util.assert(util.isSomething(ds), 'Expected a non-null defined object, got {0}', ds) util.assert(ds instanceof DataSet, 'Expected a JOSM dataset, got {0}', ds) builder.ds = ds return builder } RelationBuilder.prototype.forDataSet = forDataSet RelationBuilder.forDataSet = forDataSet /** * Create a RelationMember * * <dl> * <dt>member(role, obj)</dt> * <dd class="param-desc">Create a relation member with role <var>role</var> and member object * <var>obj</var>. <var>role</var> can be null or undefined, obj must neither * be null nor undefinde. <var>role</var> is a string, <var>obj</var> is an * OSM node, a way, or a relation. * </dd> * <dt>member(obj)</dt> * <dd class="param-desc">Create a relation member for the member object <var>obj</var>. * <var>obj</var> must neither be null nor undefinde. <var>obj</var> is an * OSM node, a way, or a relation. The created relation member has no role. * </dd> * </dl> * * @example * import {RelationBuilder, NodeBuilder} from 'josm/builder' * * // create a new RelationMember with role 'house' for a new node * const m1 = RelationBuilder.member('house', NodeBuilder.create()) * // create a new RelationMember with an empty role for a new node * const m2 = RelationBuilder.member(NodeBuilder.create()) * * @static * @returns {org.openstreetmap.josm.data.osm.RelationMember} the relation member * @summary Utility function - creates a relation member * @memberof module:josm/builder/relation~RelationBuilder * @param {string} [role] the member role * @param {primitive} primitive the member primitive */ function member () { function normalizeObj (obj) { util.assert(util.isSomething(obj), 'obj: must not be null or undefined') util.assert(obj instanceof OsmPrimitive, 'obj: expected an OsmPrimitive, got {0}', obj) return obj } function normalizeRole (role) { if (util.isNothing(role)) return null util.assert(util.isString(role), 'role: expected a string, got {0}', role) return role } let obj let role switch (arguments.length) { case 0: util.assert(false, 'Expected arguments (object) or (role, object), got 0 arguments') break case 1: obj = normalizeObj(arguments[0]) return new RelationMember(null /* no role */, obj) case 2: role = normalizeRole(arguments[0]) obj = normalizeObj(arguments[1]) return new RelationMember(role, obj) default: util.assert(false, 'Expected arguments (object) or (role, object), got {0} arguments', arguments.length) } } RelationBuilder.member = member /** * Declares the global relation id and the global relation version. * * The method can be used in a static and in an instance context. * * @example * import {RelationBuilder} from 'josm/builder' * // creates a global relation with id 1111 an version 22 * const r = RelationBuilder.withId(1111, 22).create() * * @param {number} id the global relation id. A number > 0. * @param {number} [version=1] the global relation version. If present, * a number > 0 * @returns {module:josm/builder/relation~RelationBuilder} the relation builder (for method chaining) * @memberof module:josm/builder/relation~RelationBuilder * @instance */ function withId (id, version) { const builder = receiver(this) rememberId(builder, id, version) return builder } RelationBuilder.prototype.withId = withId RelationBuilder.withId = withId /** * Declares the tags to be assigned to the new relation. * * The method can be used in a static and in an instance context. * * @example * import {RelationBuilder} from 'josm/builder' * // a new global relation with the global id 1111 and tags route='bicycle' * //and name='n8' * const r1 = RelationBuilder.withTags({name:'n8', route:'bicycle'}).create(1111) * * // a new local relation with tags name=test and highway=road * const tags = { * name : 'n8', * route : 'bicycle' * } * const r2 = RelationBuilder.withTags(tags).create() * * @param {object} [tags] the tags * @returns {module:josm/builder/relation~RelationBuilder} a relation builder (for method chaining) * @memberof module:josm/builder/relation~RelationBuilder * @instance */ function withTags (tags) { const builder = receiver(this) rememberTags(builder, tags) return builder } RelationBuilder.prototype.withTags = withTags RelationBuilder.withTags = withTags /** * Creates a new <em>proxy</em> relation. A proxy relation is a relation, * for which we only know its global id. In order to know more details * (members, tags, etc.), we would have to download it from the OSM server. * * * The method can be used in a static and in an instance context. * * @example * import {RelationBuilder} from 'josm/builder' * * // a new proxy relation for the global way with id 1111 * const r1 = RelationBuilder.createProxy(1111) * * @returns {org.openstreetmap.josm.data.osm.Relation} the new proxy relation * @memberof module:josm/builder/relation~RelationBuilder * @instance * @param {number} id the id for the proxy relation */ function createProxy (id) { const builder = receiver(this) if (util.isDef(id)) { util.assert(util.isNumber(id) && id > 0, 'Expected a number > 0, got {0}', id) builder.id = id } util.assert(util.isNumber(builder.id), 'way id is not a number. Use .createProxy(id) or ' + '.withId(id).createProxy()') util.assert(builder.id > 0, 'Expected id > 0, got {0}', builder.id) const relation = new Relation(builder.id) if (builder.ds) builder.ds.addPrimitive(relation) return relation } RelationBuilder.createProxy = createProxy RelationBuilder.prototype.createProxy = createProxy /** * Declares the members of a relation. * * Accepts either a vararg list of relation members, nodes, ways or * relations, an array of relation members, nodes ways or relations, or a * Java list of members, nodes, ways or relation. * * The method can be used in a static and in an instance context. * * @example * import {RelationBuilder, NodeBuilder} from 'josm/builder' * const {member} = RelationBuilder * const r1 = RelationBuilder.withMembers( * member('house', NodeBuilder.create()), * member('house', NodeBuilder.create()) * ) * .create() * * const members = [ * NodeBuilder.create(), // empty role * member('house', NodeBuilder.create() * ] * * const r2 = RelationBuilder.withMembers( * members, * NodeBuilder.create(), * ).create() * * @param { * ...(org.openstreetmap.josm.data.osm.OsmPrimitive * | org.openstreetmap.josm.data.osm.RelationMember * | Array.<OsmPrimitive | RelationMember> * | java.util.List) * } members the list of members. See description and examples. * @returns {module:josm/builder/relation~RelationBuilder} the relation builder (for method chaining) * @memberof module:josm/builder/relation~RelationBuilder * @instance */ function withMembers () { const builder = receiver(this) const members = [] function remember (obj) { if (util.isNothing(obj)) return if (obj instanceof OsmPrimitive) { members.push(new RelationMember(null, obj)) } else if (obj instanceof RelationMember) { members.push(obj) } else if (util.isArray(obj)) { for (let i = 0; i < obj.length; i++) remember(obj[i]) } else if (obj instanceof List) { for (let it = obj.iterator(); it.hasNext();) remember(it.next()) } else { util.assert(false, "Can''t add object ''{0}'' as relation member", obj) } } for (let i = 0; i < arguments.length; i++) { remember(arguments[i]) } builder.members = members return builder } RelationBuilder.withMembers = withMembers RelationBuilder.prototype.withMembers = withMembers function rememberMembersFromObject (builder, args) { if (!util.hasProp(args, 'members')) return const o = args.members if (!util.isSomething(o)) return util.assert(util.isArray(o) || o instanceof List, 'members: Expected an array or an instance of java.util.List, got {0}', o) builder.withMembers(o) } function initFromObject (builder, args) { rememberIdFromObject(builder, args) rememberVersionFromObject(builder, args) rememberTagsFromObject(builder, args) rememberMembersFromObject(builder, args) } /** * Named options for {@link module:josm/builder/relation~RelationBuilder#create create} * * @typedef RelationBuilderOptions * @property {number} [id] the id (> 0) of the way. Default: creates new local id. * @property {number} [version=1] the version (> 0) of the way. Default: 1. * @property {object} [tags] an object with tags. Null values and undefined * values are ignored. Any other value is converted to a string. * Leading and trailing white space in keys is removed. * @property {org.openstreetmap.josm.data.osm.RelationMember[]|java.util.List} [members] the member for the relation * @memberOf module:josm/builder/relation~RelationBuilder * @example * import {RelationBuilder, NodeBuilder} from 'josm/builder' * const {member} = RelationBuilder * // options to create a relation * const options = { * version: 3, * tags: {type: 'route'}, * members: [ * member('house', NodeBuilder.create()), * member(NodeBuilder.create()) * ] * } */ /** * Creates a new relation. * * Can be used in an instance or in a static context. * * @example * import { NodeBuilder, RelationBuilder } from 'josm/builder' * const member = RelationBuilder.member * // create a new local relation * const r1 = RelationBuilder.create() * * // create a new global relation * const r2 = RelationBuilder.create(1111) * * // create a new global relation with version 3 with some tags and two * // members * const r3 = RelationBuilder.create(2222, { * version: 3, * tags: {type: 'route'}, * members: [ * member('house', NodeBuilder.create()), * member(NodeBuilder.create()) * ] * }) * * @param {number} [id] a global way id. If missing and not set * before using <code>withId(..)</code>, creates a new local id. * @param {module:josm/builder/relation~RelationBuilder.RelationBuilderOptions} [options] options for creating the relation * @returns {org.openstreetmap.josm.data.osm.Relation} the relation * @memberof module:josm/builder/relation~RelationBuilder * @instance */ function create () { const builder = receiver(this) let arg switch (arguments.length) { case 0: break case 1: arg = arguments[0] util.assert(util.isSomething(arg), 'Argument 0: must not be null or undefined') if (util.isNumber(arg)) { util.assert(arg > 0, 'Argument 0: expected an id > 0, got {0}', arg) builder.id = arg } else if (typeof arg === 'object') { initFromObject(builder, arg) } else { util.assert(false, "Argument 0: unexpected type, got ''{0}''", arg) } break case 2: arg = arguments[0] util.assert(util.isSomething(arg), 'Argument 0: must not be null or undefined') util.assert(util.isNumber(arg), 'Argument 0: must be a number') util.assert(arg > 0, 'Expected an id > 0, got {0}', arg) builder.id = arg arg = arguments[1] if (util.isSomething(arg)) { util.assert(typeof arg === 'object', 'Argument 1: must be an object') initFromObject(builder, arg) } break default: util.assert(false, 'Unexpected number of arguments, got {0}', arguments.length) } let relation if (util.isNumber(builder.id)) { if (util.isNumber(builder.version)) { relation = new Relation(builder.id, builder.version) } else { relation = new Relation(builder.id, 1) } } else { relation = new Relation(0) // creates a new local reöatopm } assignTags(relation, builder.tags || {}) if (builder.members && builder.members.length > 0) { relation.setMembers(builder.members) } if (builder.ds) { if (builder.ds.getPrimitiveById(relation) == null) { builder.ds.addPrimitive(relation) } else { throw new Error( 'Failed to add primitive, primitive already included ' + 'in dataset. \n' + 'primitive=' + relation ) } } return relation } RelationBuilder.create = create RelationBuilder.prototype.create = create