/**
* @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