/**
* @module josm/builder/way
*/
/* 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 WayBuilder()
}
/**
* WayBuilder helps to create OSM
* {@class org.openstreetmap.josm.data.osm.Way}s.
*
* Methods of WayBuilder can be used in a static and in an instance context.
* It isn't necessary to create an instance of WayBuilder, unless it is
* configured with a {@class org.openstreetmap.josm.data.osm.DataSet},
* to which created ways are added.
* @example
* import {WayBuilder} from 'josm/builder'
* const DataSet = Java.type('org.openstreetmap.josm.data.osm.DataSet')
*
* const ds = new DataSet()
* // create a way builder without and underlying dataset ...
* let wbuilder = new WayBuilder()
* // ... with an underlying dataset ....
* wbuilder = new WayBuilder(ds)
* // ... or using this factory method
* wbuilder = WayBuilder.forDataSet(ds)
*
*
* // create a new local way
* const w1 = wbuilder.create()
*
* // create a new global way
* const w2 = wbuilder.withTags({highway: 'residential'}).create(1111)
*
* // create a new proxy for a global way
* // (an 'incomplete' node in JOSM terminology)
* const w3 = wbuilder.createProxy(2222)
*
* @class
* @param {org.openstreetmap.josm.data.osm.DataSet} [ds] a JOSM
* dataset to which created ways are added. If missing, the created ways
* aren't added to a dataset.
* @summary Helps to create OSM {@class org.openstreetmap.josm.data.osm.Way}s
* @name WayBuilder
*/
/**
* Creates a new WayBuilder with an underlying dataset.
*
* @return {module:josm/builder/way~WayBuilder} the way builder
* @param {org.openstreetmap.josm.data.osm.DataSet} ds the dataset which
* created objects are added to
* @summary Creates a new WayBuilder with an underlying dataset.
* @class
* @memberof module:josm/builder/way~WayBuilder
* @name WayBuilder
*/
export function WayBuilder(ds) {
if (util.isSomething(ds)) {
util.assert(ds instanceof DataSet, 'Expected a DataSet, got {0}', ds)
this.ds = ds
}
this.nodes = []
}
/**
* Creates or configures a WayBuilder which will add created nodes
* to the dataset <code>ds</code>.
*
* @example
* import {DataSet, WayBuilder} from 'josm/builder'
*
* // create a new way builder which builds to a data set
* const ds = new DataSet()
* let wb = WayBuilder.forDataSet(ds)
*
* @return {module:josm/builder/way~WayBuilder} the way builder
* @param {org.openstreetmap.josm.data.osm.DataSet} ds the dataset to which
* created objects are added
* @memberof module:josm/builder/way~WayBuilder
* @instance
*/
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
}
WayBuilder.prototype.forDataSet = forDataSet
WayBuilder.forDataSet = forDataSet
/**
* Declares the global way id and the global way version.
*
* The method can be used in a static and in an instance context.
*
* @example
* import {WayBuilder} from 'josm/builder'
* // creates a global way with id 1111 an version 22
* const way = WayBuilder.withId(1111, 22).create()
*
* @param {number} id (mandatory) the global way id. A number > 0.
* @param {number} [version] the global way version. If present,
* a number > 0. If missing, the version 1 is assumed.
* @return {module:josm/builder/way~WayBuilder} the way builder (for method chaining)
* @memberof module:josm/builder/way~WayBuilder
* @instance
*/
function withId (id, version) {
const builder = receiver(this)
rememberId(builder, id, version)
return builder
}
WayBuilder.prototype.withId = withId
WayBuilder.withId = withId
/**
* Declares the tags to be assigned to the new way.
*
* The method can be used in a static and in an instance context.
*
* @example
* import {WayBuilder} from 'josm/builder'
* // a new global way with the global id 1111 and tags name='Laubeggstrasse'
* // and highway=residential
* const w1 = WayBuilder.withTags({name:'Laubeggstrasse', highway:'residential'})
* .create(1111)
*
* // a new local way with tags name=test and highway=road
* const tags = {
* name : 'Laubeggstrasse',
* highway : 'residential'
* }
* const w2 = WayBuilder.withTags(tags).create()
*
* @param {object} [tags] the tags
* @return {module:josm/builder/way~WayBuilder} the way builder (for method chaining)
* @memberof module:josm/builder/way~WayBuilder
* @instance
*/
function withTags (tags) {
const builder = receiver(this)
rememberTags(builder, tags)
return builder
}
WayBuilder.prototype.withTags = withTags
WayBuilder.withTags = withTags
/**
* Declares the nodes of the way.
*
* Accepts either a vararg list of
* {@class org.openstreetmap.josm.data.osm.Node},
* an array of {@class org.openstreetmap.josm.data.osm.Node}s or a Java list
* of {@class org.openstreetmap.josm.data.osm.Node}s. At least <strong>two
* non-identical nodes</strong> have to be supplied.
* The same node can occure more than once in the list, but a consecutive
* sequence of the same node is collapsed to one node.
*
*
* The method can be used in a static and in an instance context.
*
* @example
* import {WayBuilder, NodeBuilder} from 'josm/builder'
* // creates a new local way with two local nodes
* const way = WayBuilder.withNodes(
* NodeBuilder.create(),
* NodeBuilder.create()
* ).create()
*
* @param {...org.openstreetmap.josm.data.osm.Node | java.util.List | org.openstreetmap.josm.data.osm.Node[]} [nodes] the list of nodes.
* See description and examples.
* @return {module:josm/builder/way~WayBuilder} the way builder (for method chaining)
* @memberof module:josm/builder/way~WayBuilder
* @instance
*/
function withNodes () {
const builder = receiver(this)
let nodes
switch (arguments.length) {
case 0: return builder
case 1:
nodes = arguments[0]
if (util.isNothing(nodes)) return builder
if (nodes instanceof Node) {
nodes = [nodes]
} else if (util.isArray(nodes)) {
// OK
} else if (nodes instanceof List) {
const temp = []
for (let it = nodes.iterator(); it.hasNext();) temp.push(it.next())
nodes = temp
} else {
util.assert(false,
'Argument 0: expected a Node or a list of nodes, got {0}',
nodes)
}
break
default:
nodes = Array.prototype.slice.call(arguments, 0)
break
}
const newnodes = []
let last
for (let i = 0; i < nodes.length; i++) {
const n = nodes[i]
if (util.isNothing(n)) continue
util.assert(n instanceof Node,
'Expected instances of Node only, got {0} at index {1}', n, i)
// skip sequence of identical nodes
if (last && last.getUniqueId() === n.getUniqueId()) continue
newnodes.push(n)
last = n
}
builder.nodes = newnodes
return builder
}
WayBuilder.withNodes =
WayBuilder.prototype.withNodes =
withNodes
/**
* Creates a new <em>proxy</em> way. A proxy way is a way for which we
* only know its global id. In order to know more details (nodes, 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 {WayBuilder} from 'josm/builder'
*
* // a new proxy way for the global way with id 1111
* const w1 = WayBuilder.createProxy(1111)
*
* @param {number} id the id. A number > 0
* @return {org.openstreetmap.josm.data.osm.Way} the new proxy way
* @memberof module:josm/builder/way~WayBuilder
* @instance
*/
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 way id > 0, got {0}', builder.id)
const way = new Way(builder.id)
if (builder.ds) builder.ds.addPrimitive(way)
return way
}
WayBuilder.createProxy =
WayBuilder.prototype.createProxy =
createProxy
function rememberNodesFromObject (builder, args) {
if (!util.hasProp(args, 'nodes')) return
const o = args.nodes
if (!util.isSomething(o)) return
util.assert(util.isArray(o) || o instanceof List,
'Expected an array or an instance of java.util.List, got {0}', o)
builder.withNodes(o)
}
function initFromObject (builder, args) {
rememberIdFromObject(builder, args)
rememberVersionFromObject(builder, args)
rememberTagsFromObject(builder, args)
rememberNodesFromObject(builder, args)
}
/**
* Named options for {@link module:josm/builder/way~WayBuilder#create create}
*
* @typedef WayBuilderOptions
* @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.Node[]|java.util.List} [nodes] the nodes for the way.
* @memberOf module:josm/builder/way~WayBuilder
* @example
* import {NodeBuilder} from 'josm/builder'
* // options to create a way
* const options = {
* version: 3,
* tags: {highway: 'primary'},
* nodes: [
* NodeBuilder.withPosition(1,1).create(),
* NodeBuilder.withPosition(2,2).create(),
* NodeBuilder.withPosition(3,3).create()
* ]
* }
*/
/**
* Creates a new way.
*
* Can be used in an instance or in a static context.
*
* @example
* import {WayBuilder, NodeBuilder} from 'josm/builder'
* // create a new local way
* const w1 = WayBuilder.create()
*
* // create a new global way
* const w2 = WayBuilder.create(1111)
*
* // create a new global way with version 3 with some nodes and with
* // some tags
* const w3 = WayBuilder.create(2222, {
* version: 3,
* tags: {higway: 'primary'},
* nodes: [
* NodeBuilder.withPosition(1,1).create(),
* NodeBuilder.withPosition(2,2).create(),
* NodeBuilder.withPosition(3,3).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/way~WayBuilder.WayBuilderOptions} [options] additional parameters for creating the way
* @returns {org.openstreetmap.josm.data.osm.Way} the created way
* @memberof module:josm/builder/way~WayBuilder
* @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 way
if (util.isNumber(builder.id)) {
if (util.isNumber(builder.version)) {
way = new Way(builder.id, builder.version)
} else {
way = new Way(builder.id, 1)
}
} else {
way = new Way(0) // creates a new local way
}
assignTags(way, builder.tags || {})
if (builder.nodes && builder.nodes.length > 0) {
way.setNodes(builder.nodes)
}
if (builder.ds) {
if (builder.ds.getPrimitiveById(way) == null) {
builder.ds.addPrimitive(way)
} else {
throw new Error(
'Failed to add primitive, primitive already included ' +
'in dataset. ' + 'primitive=' + way
)
}
}
return way
}
WayBuilder.create = create
WayBuilder.prototype.create = create