# http://mathling.com/geometric/point  library module

http://mathling.com/geometric/point

Point objects; 2D and 3D and 4D

Points have two kinds of accessors: "precision" accessors that return the
raw double coordinates, and basic accessors which snap the coordinates
to integer values.

Points are maps of coordinates which may be decorated with other
properties (e.g. for rendering). There are also methods for dealing with
the point coordinates as sequences: these exist mainly for efficiency in
certain cases (see noise modules and core/vector).

March 2021
Status: Incomplete, subject to refactoring

### Imports

http://mathling.com/core/utilities
```import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy"```
http://mathling.com/core/config
```import module namespace config="http://mathling.com/core/config"
at "../core/config.xqy"```
http://mathling.com/core/errors
```import module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy"```

### Functions

#### `Function: pointdeclare function point(\$x as xs:double, \$y as xs:double) as map(xs:string,item()*)`

point()
Construct a 2D point

##### Params
• x as xs:double: x coordinate
• y as xs:double: y coordinate
##### Returns
• map(xs:string,item()*)
```declare function this:point(\$x as xs:double, \$y as xs:double) as map(xs:string,item()*)
{
map {
"kind": "point",
"x": \$x,
"y": \$y,
"dim": 2
}
}```

#### ```Function: pointdeclare function point(\$x as xs:double, \$y as xs:double, \$z as xs:double) as map(xs:string, item()*)```

point()
Construct a 3D point

##### Params
• x as xs:double: x coordinate
• y as xs:double: y coordinate
• z as xs:double: z coordinate
##### Returns
• map(xs:string,item()*)
```declare function this:point(
\$x as xs:double,
\$y as xs:double,
\$z as xs:double
) as map(xs:string, item()*)
{
map {
"kind": "point",
"x": \$x,
"y": \$y,
"z": \$z,
"dim": 3
}
}```

#### ```Function: pointdeclare function point(\$x as xs:double, \$y as xs:double, \$z as xs:double, \$w as xs:double) as map(xs:string, item()*)```

point()
Construct a 4D point

##### Params
• x as xs:double: x coordinate
• y as xs:double: y coordinate
• z as xs:double: z coordinate
• w as xs:double: w coordinate
##### Returns
• map(xs:string,item()*)
```declare function this:point(
\$x as xs:double,
\$y as xs:double,
\$z as xs:double,
\$w as xs:double
) as map(xs:string, item()*)
{
map {
"kind": "point",
"x": \$x,
"y": \$y,
"z": \$z,
"w": \$w,
"dim": 4
}
}```

#### `Function: vectordeclare function vector(\$coordinates as xs:double*) as map(xs:string,item()*)`

vector()
Construct a point of some dimension from a series of coordinates.
Useful for genericizing some of the noise code. It is an error if the
number of coordinates is not 2, 3, or 4. Coordinates are given in the
(x, y, z w) order.

##### Params
• coordinates as xs:double*: the coordinates
##### Returns
• map(xs:string,item()*)
```declare function this:vector(
\$coordinates as xs:double*
) as map(xs:string,item()*)
{
switch (count(\$coordinates))
case 2 return this:point(\$coordinates, \$coordinates)
case 3 return this:point(\$coordinates, \$coordinates, \$coordinates)
case 4 return this:point(\$coordinates, \$coordinates, \$coordinates, \$coordinates)
default return errors:error("GEOM-UNKDIM", (count(\$coordinates), \$coordinates))
}```

#### ```Function: as-dimensiondeclare function as-dimension(\$point as map(xs:string,item()*), \$dim as xs:integer) as map(xs:string,item()*)```

as-dimension()
Reconstitute a point as a point with the given number of dimensions.
If we are reducing dimensions, just remove excess coordinates.
If we are expanding dimensions, fill in with 0.

Examples:
point:as-dimension(point:point(1,2), 3) == point:point(1,2,0)
point:as-dimension(point:point(1,2,3), 3) == point:point(1,2)

##### Params
• point as map(xs:string,item()*): source point
• dim as xs:integer: dimension to cast point to
##### Returns
• map(xs:string,item()*)
```declare function this:as-dimension(
\$point as map(xs:string,item()*),
\$dim as xs:integer
) as map(xs:string,item()*)
{
let \$v := this:vector(this:pcoordinates(\$point, \$dim))
return
if (empty(this:properties(\$point))) then \$v
else util:merge-into(\$point, \$v)
}```

#### `Function: dimensiondeclare function dimension(\$point as map(xs:string,item()*)) as xs:integer`

dimension()
Return the dimension of the point.

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:integer
```declare function this:dimension(\$point as map(xs:string,item()*)) as xs:integer
{
(
\$point("dim"),
if (\$point=>map:contains("w")) then 4
else if (\$point=>map:contains("z")) then 3
else if (\$point=>map:contains("y")) then 2
else 0 (: Dunno :)
)
}```

#### `Function: max-dimensiondeclare function max-dimension(\$points as map(xs:string,item()*)*) as xs:integer`

max-dimension()
Convenience method to return the maximum dimension of a set of points.

##### Params
• points as map(xs:string,item()*)*: sequence of points
##### Returns
• xs:integer
```declare function this:max-dimension(\$points as map(xs:string,item()*)*) as xs:integer
{
max(for \$pt in \$points return this:dimension(\$pt))
}```

#### `Function: xdeclare function x(\$point as map(xs:string,item()*)) as xs:integer`

x()
Accessor for the snapped x coordinate

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:integer
```declare function this:x(\$point as map(xs:string,item()*)) as xs:integer
{
if (\$point("x") = xs:double("INF")) then \$util:UINT64_MAX
else if (\$point("x") = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$point("x")) cast as xs:integer
}```

#### `Function: ydeclare function y(\$point as map(xs:string,item()*)) as xs:integer`

y()
Accessor for the snapped y coordinate

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:integer
```declare function this:y(\$point as map(xs:string,item()*)) as xs:integer
{
if (\$point("y") = xs:double("INF")) then \$util:UINT64_MAX
else if (\$point("y") = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$point("y")) cast as xs:integer
}```

#### `Function: zdeclare function z(\$point as map(xs:string,item()*)) as xs:integer`

z()
Accessor for the snapped z coordinate; return 0 for 2D point

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:integer
```declare function this:z(\$point as map(xs:string,item()*)) as xs:integer
{
return
if (\$z = xs:double("INF")) then \$util:UINT64_MAX
else if (\$z = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$z) cast as xs:integer
}```

#### `Function: wdeclare function w(\$point as map(xs:string,item()*)) as xs:integer`

w()
Accessor for the snapped w coordinate; return 0 for 2D or 3D point

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:integer
```declare function this:w(\$point as map(xs:string,item()*)) as xs:integer
{
return
if (\$w = xs:double("INF")) then \$util:UINT64_MAX
else if (\$w = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$w) cast as xs:integer
}```

#### `Function: pxdeclare function px(\$point as map(xs:string,item()*)) as xs:double`

px()
Accessor for the raw precision x coordinate

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:double
```declare function this:px(\$point as map(xs:string,item()*)) as xs:double
{
\$point("x")
}```

#### `Function: pydeclare function py(\$point as map(xs:string,item()*)) as xs:double`

py()
Accessor for the raw precision y coordinate

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:double
```declare function this:py(\$point as map(xs:string,item()*)) as xs:double
{
\$point("y")
}```

#### `Function: pzdeclare function pz(\$point as map(xs:string,item()*)) as xs:double`

pz()
Accessor for the raw precision z coordinate; returns 0 for 2D point

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:double
```declare function this:pz(\$point as map(xs:string,item()*)) as xs:double
{
}```

#### `Function: pwdeclare function pw(\$point as map(xs:string,item()*)) as xs:double`

pw()
Accessor for the raw precision w coordinate; returns 0 for 2D or 3D point

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:double
```declare function this:pw(\$point as map(xs:string,item()*)) as xs:double
{
}```

#### `Function: coordinatesdeclare function coordinates(\$point as map(xs:string,item()*)) as xs:integer*`

coordinates()
Return snapped coordinates of the point as a sequence.

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:integer*
```declare function this:coordinates(\$point as map(xs:string,item()*)) as xs:integer*
{
let \$d := this:dimension(\$point)
return (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:x(\$point), this:y(\$point)) else (),
if (\$d >= 3) then this:z(\$point) else (),
if (\$d >= 4) then this:w(\$point) else ()
)
}```

#### `Function: coordinatesdeclare function coordinates(\$point as map(xs:string,item()*), \$d as xs:integer) as xs:integer*`

coordinates()
Return snapped coordinates of the point as a sequence, treating the
point as having a given dimension.

##### Params
• point as map(xs:string,item()*): the point
• d as xs:integer: desired dimension
##### Returns
• xs:integer*
```declare function this:coordinates(\$point as map(xs:string,item()*), \$d as xs:integer) as xs:integer*
{
if (\$d = 0) then this:coordinates(\$point)
else (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:x(\$point), this:y(\$point)) else (),
if (\$d >= 3) then this:z(\$point) else (),
if (\$d >= 4) then this:w(\$point) else ()
)
}```

#### `Function: pcoordinatesdeclare function pcoordinates(\$point as map(xs:string,item()*)) as xs:double*`

pcoordinates()
Return raw precision coordinates of the point as a sequence.

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:double*
```declare function this:pcoordinates(\$point as map(xs:string,item()*)) as xs:double*
{
let \$d := this:dimension(\$point)
return (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:px(\$point), this:py(\$point)) else (),
if (\$d >= 3) then this:pz(\$point) else (),
if (\$d >= 4) then this:pw(\$point) else ()
)
}```

#### `Function: pcoordinatesdeclare function pcoordinates(\$point as map(xs:string,item()*), \$d as xs:integer) as xs:double*`

pcoordinates()
Return raw precision coordinates of the point as a sequence, treating the
point as having a given dimension.

##### Params
• point as map(xs:string,item()*): the point
• d as xs:integer: the desired dimension
##### Returns
• xs:double*
```declare function this:pcoordinates(\$point as map(xs:string,item()*), \$d as xs:integer) as xs:double*
{
if (\$d = 0) then this:pcoordinates(\$point)
else (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:px(\$point), this:py(\$point)) else (),
if (\$d >= 3) then this:pz(\$point) else (),
if (\$d >= 4) then this:pw(\$point) else ()
)
}```

#### `Function: pcoordinatedeclare function pcoordinate(\$point as map(xs:string,item()*), \$ix as xs:integer) as xs:double`

pcoordinate()
Return the given raw precision coordinates of the point.

##### Params
• point as map(xs:string,item()*): the point
• ix as xs:integer: index of the coordinate, e.g. 1=1st, etc.
##### Returns
• xs:double
```declare function this:pcoordinate(\$point as map(xs:string,item()*), \$ix as xs:integer) as xs:double
{
switch (\$ix)
case 1 return this:px(\$point)
case 2 return this:py(\$point)
case 3 return this:pz(\$point)
case 4 return this:pw(\$point)
default return errors:error("GEOM-UNKDIM", (\$ix, \$point))
}```

#### `Function: kinddeclare function kind(\$point as map(xs:string,item()*)) as xs:string`

kind()
Accessor for the kind of data this is; for debugging and polymorphic geo
functions

##### Params
• point as map(xs:string,item()*): the point
##### Returns
• xs:string
```declare function this:kind(\$point as map(xs:string,item()*)) as xs:string
{
\$point("kind")
}```

#### `Function: property-mapdeclare function property-map(\$region as map(xs:string,item()*)) as map(xs:string, item()*)`

property-map()
Return the annotation properties of the point as a map. Check whether this
is actually a point.

##### Params
• region as map(xs:string,item()*): the region
##### Returns
• map(xs:string,item()*)
```declare function this:property-map(\$region as map(xs:string,item()*)) as map(xs:string, item()*)
{
switch(this:kind(\$region))
case "point" return util:exclude(\$region, \$this:RESERVED)
default return map {}
}```

#### `Function: propertiesdeclare function properties(\$region as map(xs:string,item()*)) as xs:string*`

properties()
Return the names of the annotation properties of the point.
Check whether this is actually a point.

##### Params
• region as map(xs:string,item()*): the region
##### Returns
• xs:string*
```declare function this:properties(\$region as map(xs:string,item()*)) as xs:string*
{
switch(this:kind(\$region))
case "point" return (\$region=>map:keys())[not(. = \$this:RESERVED)]
default return ()
}```

#### ```Function: with-propertiesdeclare function with-properties(\$region as map(xs:string,item()*), \$properties as map(xs:string, item()*)) as map(xs:string, item()*)```

with-properties()
Annotate the point with some new properties and return the new point.
Will not touch any of the core properties. Will override existing properties
with the same keys but leave properties with different keys in place.
Raises an error if this is not actually a point.

##### Params
• region as map(xs:string,item()*): the region
• properties as map(xs:string,item()*): the properties
##### Returns
• map(xs:string,item()*)
```declare function this:with-properties(
\$region as map(xs:string,item()*),
\$properties as map(xs:string, item()*)
) as map(xs:string, item()*)
{
switch(this:kind(\$region))
case "point" return
util:merge-into(\$region, util:exclude(\$properties,\$this:RESERVED))
}```

#### `Function: midpointdeclare function midpoint(\$a as map(xs:string,item()*), \$b as map(xs:string,item()*)) as map(xs:string,item()*)`

midpoint()
Return midpoint between two points

##### Params
• a as map(xs:string,item()*): one point
• b as map(xs:string,item()*): the other
##### Returns
• map(xs:string,item()*)
```declare function this:midpoint(\$a as map(xs:string,item()*), \$b as map(xs:string,item()*)) as map(xs:string,item()*)
{
this:map2(
function (\$ca as xs:double, \$cb as xs:double) as xs:double {
(\$ca + \$cb) div 2
},
\$a,
\$b
)
}```

#### `Function: magnitudedeclare function magnitude(\$v as map(xs:string,item()*)) as xs:double`

magnitude()
Magnitude of the point as vector from origin.

##### Params
• v as map(xs:string,item()*): the point
##### Returns
• xs:double
```declare function this:magnitude(\$v as map(xs:string,item()*)) as xs:double
{
math:sqrt( this:dot2(\$v) )
}```

#### ```Function: dotdeclare function dot(\$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*)) as xs:double```

dot()
Compute the dot product of two points

##### Params
• p1 as map(xs:string,item()*): one point
• p2 as map(xs:string,item()*): another point
##### Returns
• xs:double
```declare function this:dot(
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p1, \$p2))
return (
sum(
util:zip(
function (\$c1 as xs:double, \$c2 as xs:double) as xs:double {
\$c1*\$c2
},
this:pcoordinates(\$p1, \$d),
this:pcoordinates(\$p2, \$d)
)
)
)
}```

#### `Function: dot2declare function dot2(\$p as map(xs:string,item()*)) as xs:double`

dot2()
Compute the dot product of a point with itself

##### Params
• p as map(xs:string,item()*): the point
##### Returns
• xs:double
```declare function this:dot2(
\$p as map(xs:string,item()*)
) as xs:double
{
sum(
for-each(this:pcoordinates(\$p), function (\$c as xs:double) as xs:double { \$c*\$c })
)
}```

#### ```Function: determinantdeclare function determinant(\$u as map(xs:string,item()*), \$v as map(xs:string,item()*)) as xs:double```

determinant()
Compute the determinant of two 2D points.

##### Params
• u as map(xs:string,item()*): one point
• v as map(xs:string,item()*): another point
##### Returns
• xs:double
```declare function this:determinant(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as xs:double
{
\$u=>this:px()*\$v=>this:py() - \$u=>this:py()*\$v=>this:px()
}```

#### ```Function: crossdeclare function cross(\$u as map(xs:string,item()*), \$v as map(xs:string,item()*)) as map(xs:string,item()*)```

cross()
Compute the cross product of two 3D points.

##### Params
• u as map(xs:string,item()*): one point
• v as map(xs:string,item()*): another point
##### Returns
• map(xs:string,item()*)
```declare function this:cross(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
this:point(
\$u=>this:py()*\$v=>this:pz() - \$u=>this:pz()*\$v=>this:py(),
\$u=>this:pz()*\$v=>this:px() - \$u=>this:px()*\$v=>this:pz(),
\$u=>this:px()*\$v=>this:py() - \$u=>this:py()*\$v=>this:px()
)
}```

#### ```Function: timesdeclare function times(\$u as map(xs:string,item()*), \$k as xs:double) as map(xs:string,item()*)```

times()
Scale every coordinate by a constant, returning the new point.

##### Params
• u as map(xs:string,item()*): point to scale
• k as xs:double: constant multiplier
##### Returns
• map(xs:string,item()*)
```declare function this:times(
\$u as map(xs:string,item()*),
\$k as xs:double
) as map(xs:string,item()*)
{
this:map(function (\$c as xs:double) as xs:double {\$k * \$c}, \$u)
}```

#### ```Function: adddeclare function add(\$u as map(xs:string,item()*), \$v as map(xs:string,item()*)) as map(xs:string,item()*)```

Add two points, returning the new point.

##### Params
• u as map(xs:string,item()*): one point
• v as map(xs:string,item()*): the other point
##### Returns
• map(xs:string,item()*)
```declare function this:add(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
this:map2(function (\$a as xs:double, \$b as xs:double) as xs:double {\$a + \$b}, \$u, \$v)
}```

#### `Function: sumdeclare function sum(\$pts as map(xs:string,item()*)*) as map(xs:string,item()*)`

sum()
Add a series of points, returning the new point.

##### Params
• pts as map(xs:string,item()*)*: the points
##### Returns
• map(xs:string,item()*)
```declare function this:sum(
\$pts as map(xs:string,item()*)*
) as map(xs:string,item()*)
{
this:vector(
for \$d in 1 to this:max-dimension(\$pts) return (
sum(\$pts!this:pcoordinate(., \$d))
)
)
}```

#### ```Function: subdeclare function sub(\$u as map(xs:string,item()*), \$v as map(xs:string,item()*)) as map(xs:string,item()*)```

sub()
Subtract one point from another, returning the new point.

##### Params
• u as map(xs:string,item()*): one point
• v as map(xs:string,item()*): the other point
##### Returns
• map(xs:string,item()*)
```declare function this:sub(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
this:map2(function (\$a as xs:double, \$b as xs:double) as xs:double {\$a - \$b}, \$u, \$v)
}```

#### `Function: minusdeclare function minus(\$v as map(xs:string,item()*)) as map(xs:string,item()*)`

minus()
Negate the coordinates of the point, returning the new point.

##### Params
• v as map(xs:string,item()*): the point
##### Returns
• map(xs:string,item()*)
```declare function this:minus(\$v as map(xs:string,item()*))
as map(xs:string,item()*)
{
this:map(function (\$a as xs:double) as xs:double {-\$a}, \$v)
}```

#### `Function: normalizedeclare function normalize(\$v as map(xs:string,item()*)) as map(xs:string,item()*)`

normalize()
Normalize a vector represented as a point (scale to unit vector), returning
the new point.

##### Params
• v as map(xs:string,item()*): the point
##### Returns
• map(xs:string,item()*)
```declare function this:normalize(
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$l := this:magnitude(\$v)
return
if (\$l = 0) then \$v
else this:map(function (\$c as xs:double) as xs:double {\$c div \$l}, \$v)
}```

#### `Function: perpendiculardeclare function perpendicular(\$v as map(xs:string, item()*)) as map(xs:string, item()*)`

perpendicular()
Compute the perpendicular vector for a 2D vector represented as a point.
(Rotated by 90 degrees.)

##### Params
• v as map(xs:string,item()*): the point
##### Returns
• map(xs:string,item()*)
```declare function this:perpendicular(
\$v as map(xs:string, item()*)
) as map(xs:string, item()*)
{
this:point(\$v=>this:py(), -(\$v=>this:px()))
}```

#### `Function: perpendicularsdeclare function perpendiculars(\$v as map(xs:string,item()*)) as map(xs:string,item()*)*`

perpendiculars()
Compute both perpendicular vectors for a 2D vector represented as a point.
Rotated by 90 degrees and by -90 degrees, in that order.

##### Params
• v as map(xs:string,item()*): the point
##### Returns
• map(xs:string,item()*)*
```declare function this:perpendiculars(\$v as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:point(this:py(\$v), -this:px(\$v)),
this:point(-this:py(\$v), this:px(\$v))
}```

#### `Function: perpendiculars-with-zdeclare function perpendiculars-with-z(\$v as map(xs:string,item()*)) as map(xs:string,item()*)*`

perpendiculars-with-z()
Compute perpendicular vectors for a 3D vector represented as a point.
Rotated in y by 90 degrees and by -90 degrees, in that order, with z
rotated in the corresponding direction. For spline/smoothing.

##### Params
• v as map(xs:string,item()*): the point
##### Returns
• map(xs:string,item()*)*
```declare function this:perpendiculars-with-z(\$v as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:point(this:py(\$v), -this:px(\$v), this:pz(\$v)),
this:point(-this:py(\$v), this:px(\$v), -this:pz(\$v))
}```

#### `Function: snapdeclare function snap(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)*`

snap()
Snap the coordinates of the points, returning the points with snapped
(i.e. integer) coordinates. NaN snaps to UINT_MAX

##### Params
• points as map(xs:string,item()*)*: the points
##### Returns
• map(xs:string,item()*)*
```declare function this:snap(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
for \$point in \$points return (
this:map(
function (\$c as xs:double) as xs:integer {
if (\$c = xs:double("INF") or not(\$c = \$c)) then \$util:UINT64_MAX
else if (\$c = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$c) cast as xs:integer
},
\$point
)
)
}```

#### `Function: floordeclare function floor(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)*`

floor()
Return points where all the coordinates have been set to the floor of
the original points' values. Differs from snap() for negative coordinates.

##### Params
• points as map(xs:string,item()*)*: the points
##### Returns
• map(xs:string,item()*)*
```declare function this:floor(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
for \$point in \$points return (
this:map(fn:floor#1, \$point)
)
}```

#### ```Function: decimaldeclare function decimal(\$points as map(xs:string,item()*)*, \$digits as xs:integer) as map(xs:string,item()*)*```

decimal()
Perform decimal rounding on all the coordinates (see util:decimal).

##### Params
• points as map(xs:string,item()*)*: the points to round
• digits as xs:integer: how many digits after the decimal point to keep
##### Returns
• map(xs:string,item()*)*
```declare function this:decimal(
\$points as map(xs:string,item()*)*,
\$digits as xs:integer
) as map(xs:string,item()*)*
{
for \$point in \$points return (
this:map(function (\$c as xs:double) as xs:double {util:decimal(\$c, \$digits)}, \$point)
)
}```

#### `Function: quotedeclare function quote(\$points as map(xs:string,item()*)*) as xs:string`

quote()
Return a string value for the points, suitable for debugging.

##### Params
• points as map(xs:string,item()*)*: the point sequence to quote
##### Returns
• xs:string
```declare function this:quote(\$points as map(xs:string,item()*)*) as xs:string
{
string-join(
for \$point in \$points return (
string-join(this:pcoordinates(\$point)!string(.),",")
)
,
" "
)
}```

#### `Function: validdeclare function valid(\$point as map(xs:string,item()*)) as xs:boolean`

valid()
False if a coordinate is INF or NaN

##### Params
• point as map(xs:string,item()*)
##### Returns
• xs:boolean
```declare function this:valid(\$point as map(xs:string,item()*)) as xs:boolean
{
every \$c in this:pcoordinates(\$point)
satisfies \$c=\$c and not(\$c=(xs:double("INF"),xs:double("-INF")))
}```

#### ```Function: samedeclare function same(\$this as map(xs:string,item()*), \$other as map(xs:string,item()*)) as xs:boolean```

same()
Equality comparison for points, ignoring annotation properties.
Return true() if they have equal coordinates.

##### Params
• this as map(xs:string,item()*): one point
• other as map(xs:string,item()*): the point to compare it to
##### Returns
• xs:boolean
```declare function this:same(
\$this as map(xs:string,item()*),
\$other as map(xs:string,item()*)
) as xs:boolean
{
let \$d := this:max-dimension((\$this, \$other))
return (
deep-equal(
this:pcoordinates(\$this, \$d),
this:pcoordinates(\$other, \$d)
)
)
}```

#### ```Function: samedeclare function same(\$this as map(xs:string,item()*), \$other as map(xs:string,item()*), \$tolerance as xs:double) as xs:boolean```

same()
Equality comparison for points, ignoring annotation properties.
Return true() if they have equal coordinates within the given tolerance.

##### Params
• this as map(xs:string,item()*): one point
• other as map(xs:string,item()*): the point to compare it to
• tolerance as xs:double: allowable slop
##### Returns
• xs:boolean
```declare function this:same(
\$this as map(xs:string,item()*),
\$other as map(xs:string,item()*),
\$tolerance as xs:double
) as xs:boolean
{
let \$d := this:max-dimension((\$this, \$other))
return (
util:every(
util:zip(
function (\$c1 as xs:double, \$c2 as xs:double) as xs:boolean {
abs(\$c1 - \$c2) < \$tolerance
},
this:pcoordinates(\$this, \$d),
this:pcoordinates(\$other, \$d)
)
)
)
}```

#### ```Function: mutatedeclare function mutate(\$regions as map(xs:string,item()*)*, \$mutate as function (map(xs:string,item()*)) as map(xs:string,item()*)) as map(xs:string,item()*)*```

mutate()
Run a function over a sequence of points to produce a new sequence of
points.

##### Params
• regions as map(xs:string,item()*)*: input sequence of points
• mutate as function(map(xs:string,item()*))asmap(xs:string,item()*): function that takes a point as an argument and returns a new point
##### Returns
• map(xs:string,item()*)*
```declare function this:mutate(
\$regions as map(xs:string,item()*)*,
\$mutate as function (map(xs:string,item()*)) as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
\$regions!\$mutate(.)
}```

#### `Function: distinctdeclare function distinct(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)*`

distinct()
Return the points in the sequence that have distinct coordinates from
each other.

##### Params
• points as map(xs:string,item()*)*: the sequence of points
##### Returns
• map(xs:string,item()*)*
```declare function this:distinct(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
if (empty(\$points)) then () else
let \$max-d := this:max-dimension(\$points)
let \$sorted :=
sort(\$points, (), function (\$pt as map(xs:string,item()*)) as xs:double* {this:pcoordinates(\$pt, \$max-d)})
return (
for \$pt at \$i in tail(\$sorted)
where not(this:same(\$pt, \$sorted[\$i]))
return \$pt
)
}```

#### `Function: unquotedeclare function unquote(\$string as xs:string) as map(xs:string,item()*)`

unquote()
Parse a string representation of a point and produce a point.
The string consists of the coordinates in x,y,z,w order with commas and
optional spaces in between.
Example "3.4, 1" == point:point(3.4, 1)

##### Params
• string as xs:string: the input string to parse
##### Returns
• map(xs:string,item()*)
```declare function this:unquote(
\$string as xs:string
) as map(xs:string,item()*)
{
this:vector(tokenize(\$string, "[ ]*,[ ]*")!xs:double(.))
}```

#### ```Function: mapdeclare function map(\$f as function(xs:double) as xs:double, \$point as map(xs:string,item()*)) as map(xs:string,item()*)```

map()
Map a function over the coordinates of a point, returning the new point.

##### Params
• f as function(xs:double)asxs:double: function from coordinate to coordinate
• point as map(xs:string,item()*): input point
##### Returns
• map(xs:string,item()*)
```declare function this:map(
\$f as function(xs:double) as xs:double,
\$point as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$v := this:vector(fn:for-each(this:pcoordinates(\$point), \$f))
return (
if (empty(this:properties(\$point))) then \$v
else util:merge-into(\$point, \$v)
)
}```

#### ```Function: mapdeclare function map(\$f as function(xs:double) as xs:double, \$point as map(xs:string,item()*), \$d as xs:integer) as map(xs:string,item()*)```

map()
Map a function over the coordinates of a point, returning the new point.
Treat the point as having a particular dimension.

##### Params
• f as function(xs:double)asxs:double: function from coordinate to coordinate
• point as map(xs:string,item()*): input point
• d as xs:integer: the effective dimension of the point
##### Returns
• map(xs:string,item()*)
```declare function this:map(
\$f as function(xs:double) as xs:double,
\$point as map(xs:string,item()*),
\$d as xs:integer
) as map(xs:string,item()*)
{
let \$v := this:vector(fn:for-each(this:pcoordinates(\$point, \$d), \$f))
return (
if (empty(this:properties(\$point))) then \$v
else util:merge-into(\$point, \$v)
)
}```

#### ```Function: map2declare function map2(\$f as function(xs:double, xs:double) as xs:double, \$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*)) as map(xs:string,item()*)```

map2()
Map a function over the coordinates of two points, pair by pair, returning
the new point. That is, apply function to the x coordinates, then the
y coordinates, and so on.

##### Params
• f as function(xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
• p1 as map(xs:string,item()*): one input point
• p2 as map(xs:string,item()*): another input point
##### Returns
• map(xs:string,item()*)
```declare function this:map2(
\$f as function(xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$d := this:max-dimension((\$p1, \$p2))
return
this:vector(
util:zip(\$f, this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d))
)
}```

#### ```Function: map2declare function map2(\$f as function(xs:double, xs:double) as xs:double, \$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*), \$d as xs:integer) as map(xs:string,item()*)```

map2()
Map a function over the coordinates of two points, pair by pair, returning
the new point. That is, apply function to the x coordinates, then the
y coordinates, and so on. Treat the point as having a particular dimension.

##### Params
• f as function(xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
• p1 as map(xs:string,item()*): one input point
• p2 as map(xs:string,item()*): another input point
• d as xs:integer: the effective dimension of the points
##### Returns
• map(xs:string,item()*)
```declare function this:map2(
\$f as function(xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$d as xs:integer
) as map(xs:string,item()*)
{
this:vector(
util:zip(\$f, this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d))
)
}```

#### ```Function: map3declare function map3(\$f as function(xs:double, xs:double, xs:double) as xs:double, \$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*), \$p3 as map(xs:string,item()*)) as map(xs:string, item()*)```

map3()
Map a function over the coordinates of three points, triple by triple,
returning the new point. That is, apply function to the x coordinates,
then the y coordinates, and so on.

##### Params
• f as function(xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
• p1 as map(xs:string,item()*): one input point
• p2 as map(xs:string,item()*): another input point
• p3 as map(xs:string,item()*): third input point
##### Returns
• map(xs:string,item()*)
```declare function this:map3(
\$f as function(xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*)
) as map(xs:string, item()*)
{
let \$d := this:max-dimension((\$p1, \$p2, \$p3))
return
this:vector(
util:zip(
function (\$a as xs:double, \$fc as function(xs:double) as xs:double) {\$fc(\$a)},
this:pcoordinates(\$p3, \$d),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?) },
this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d)
)
)
)
}```

#### ```Function: map3declare function map3(\$f as function(xs:double, xs:double, xs:double) as xs:double, \$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*), \$p3 as map(xs:string,item()*), \$d as xs:integer) as map(xs:string, item()*)```

map3()
Map a function over the coordinates of three points, triple by triple,
returning the new point. That is, apply function to the x coordinates,
then the y coordinates, and so on. Treat the point as having a
particular dimension.

##### Params
• f as function(xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
• p1 as map(xs:string,item()*): one input point
• p2 as map(xs:string,item()*): another input point
• p3 as map(xs:string,item()*): third input point
• d as xs:integer: the effective dimension of the points
##### Returns
• map(xs:string,item()*)
```declare function this:map3(
\$f as function(xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*),
\$d as xs:integer
) as map(xs:string, item()*)
{
this:vector(
util:zip(
function (\$a as xs:double, \$fc as function(xs:double) as xs:double) as xs:double {\$fc(\$a)},
this:pcoordinates(\$p3, \$d),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?) },
this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d)
)
)
)
}```

#### ```Function: map4declare function map4(\$f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, \$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*), \$p3 as map(xs:string,item()*), \$p4 as map(xs:string,item()*)) as map(xs:string, item()*)```

map4()
Map a function over the coordinates of four points, coordinate by
coordinate, returning the new point. That is, apply function to the x
coordinates, then the y coordinates, and so on.

##### Params
• f as function(xs:double,xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
• p1 as map(xs:string,item()*): one input point
• p2 as map(xs:string,item()*): another input point
• p3 as map(xs:string,item()*): third input point
• p4 as map(xs:string,item()*): fourth input point
##### Returns
• map(xs:string,item()*)
```declare function this:map4(
\$f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*),
\$p4 as map(xs:string,item()*)
) as map(xs:string, item()*)
{
let \$dim := this:max-dimension((\$p1, \$p2, \$p3))
return
this:vector(
util:zip(
function (\$d as xs:double, \$fd as function(xs:double) as xs:double) as xs:double {\$fd(\$d)},
this:pcoordinates(\$p4, \$dim),
util:zip(
function (\$c as xs:double, \$fc as function(xs:double, xs:double) as xs:double) as function(*) {\$fc(\$c, ?)},
this:pcoordinates(\$p3, \$dim),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?, ?) },
this:pcoordinates(\$p1, \$dim), this:pcoordinates(\$p2, \$dim)
)
)
)
)
}```

#### ```Function: map4declare function map4(\$f as function(xs:double, xs:double, xs:double, xs:double) as xs:double, \$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*), \$p3 as map(xs:string,item()*), \$p4 as map(xs:string,item()*), \$dim as xs:integer) as map(xs:string, item()*)```

map4()
Map a function over the coordinates of four points, coordinate by
coordinate, returning the new point. That is, apply function to the x
coordinates, then the y coordinates, and so on. Treat the point as having a
particular dimension.

##### Params
• f as function(xs:double,xs:double,xs:double,xs:double)asxs:double: function from coordinate pairs to coordinate
• p1 as map(xs:string,item()*): one input point
• p2 as map(xs:string,item()*): another input point
• p3 as map(xs:string,item()*): third input point
• p4 as map(xs:string,item()*): fourth input point
• dim as xs:integer
##### Returns
• map(xs:string,item()*)
```declare function this:map4(
\$f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*),
\$p4 as map(xs:string,item()*),
\$dim as xs:integer
) as map(xs:string, item()*)
{
this:vector(
util:zip(
function (\$d as xs:double, \$fd as function(xs:double) as xs:double) as xs:double {\$fd(\$d)},
this:pcoordinates(\$p4, \$dim),
util:zip(
function (\$c as xs:double, \$fc as function(xs:double, xs:double) as xs:double) as function(*) {\$fc(\$c, ?)},
this:pcoordinates(\$p3, \$dim),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?, ?) },
this:pcoordinates(\$p1, \$dim), this:pcoordinates(\$p2, \$dim)
)
)
)
)
}```

#### ```Function: distancedeclare function distance(\$p1 as map(xs:string,item()*), \$p2 as map(xs:string,item()*)) as xs:double```

distance()
Euclidean distance between points

##### Params
• p1 as map(xs:string,item()*)
• p2 as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:distance(
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p1, \$p2))
return (
math:sqrt(
sum(
util:zip(
function (\$c1 as xs:double, \$c2 as xs:double) as xs:double {
(\$c1 - \$c2)*(\$c1 - \$c2)
},
this:pcoordinates(\$p1, \$d),
this:pcoordinates(\$p2, \$d)
)
)
)
)
}```

#### ```Function: polar-distancedeclare function polar-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

polar-distance()
Angular distance between points taken as vectors.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:polar-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
abs(this:angle(\$this:ORIGIN2D, \$p) - this:angle(\$this:ORIGIN2D, \$q))
}```

#### ```Function: taxi-distancedeclare function taxi-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

taxi-distance()
Taxi-cab distance between two points. That is, right angle distance, such as
a taxi going 2 blocks North plus 3 blocks East = distance 5 blocks.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:taxi-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
sum(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
abs(\$cp - \$cq)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
)
)
}```

#### ```Function: avg-distancedeclare function avg-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

avg-distance()
Average of distances along each dimension.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:avg-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
avg(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
abs(\$cp - \$cq)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
)
)
}```

#### ```Function: rail-distancedeclare function rail-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

rail-distance()
Railway distance, e.g distance to the central hub and back out.
The hub is the origin.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:rail-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
this:distance(\$p, \$this:ORIGIN) + this:distance(\$q, \$this:ORIGIN)
}```

#### ```Function: rail-distancedeclare function rail-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*), \$origin as map(xs:string,item()*)) as xs:double```

rail-distance()
Railway distance, e.g distance to the central hub and back out where the hub
is specified.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
• origin as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:rail-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$origin as map(xs:string,item()*)
) as xs:double
{
this:distance(\$p, \$origin) + this:distance(\$q, \$origin)
}```

#### ```Function: chebyshev-distancedeclare function chebyshev-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

chebyshev-distance()
Chebyshev distance metric. The maximum of the dimensional distances.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:chebyshev-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
max(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
abs(\$cp - \$cq)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
)
)
}```

#### ```Function: minkowski-distancedeclare function minkowski-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*), \$order as xs:double) as xs:double```

minkokski-distance()
Minkowski distance metric of the given order. Certain orders give rise to
other metrics:
order=1=>taxi; order=2=>Euclidean; as order=>∞=>Chebyshev;
order<1 is not a metric unless undo the final pow. i.e. X is a metric but
X^(1/order) is not.

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
• order as xs:double
##### Returns
• xs:double
```declare function this:minkowski-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$order as xs:double
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
math:pow(
sum(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
math:pow(abs(\$cp - \$cq), \$order)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
),
1 div \$order
)
)
}```

#### ```Function: cosine-similaritydeclare function cosine-similarity(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

cosine-similarity()
Cosine similarity: (A·B)/(∥A∥∥B∥) = ΣAiBi/(√Ai²√Bi²)

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:cosine-similarity(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
this:dot(\$p, \$q) div (this:magnitude(\$p)*this:magnitude(\$q))
}```

#### ```Function: cosine-distancedeclare function cosine-distance(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*)) as xs:double```

cosine-distance()
Cosine similarity as a metric: acos(similarity)/π => [0,1]

##### Params
• p as map(xs:string,item()*)
• q as map(xs:string,item()*)
##### Returns
• xs:double
```declare function this:cosine-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
math:acos(this:cosine-similarity(\$p, \$q)) div math:pi()
}```

#### `Function: angledeclare function angle(\$last as map(xs:string,item()*)?, \$curr as map(xs:string,item()*)) as xs:double`

angle()
Compute the angle (azimuth) from one point to the next, in degrees
Return 0 if points are the same

##### Params
• last as map(xs:string,item()*)?: previous point; use (0,0) if no previous
• curr as map(xs:string,item()*): current point
##### Returns
• xs:double
```declare function this:angle(\$last as map(xs:string,item()*)?, \$curr as map(xs:string,item()*)) as xs:double
{
let \$this := \$curr=>this:as-dimension(2)
let \$prev := this:as-dimension((\$last,\$this:ORIGIN),2)
return
if (this:same(\$prev, \$this) or (this:distance(\$prev,\$this) < \$config:ε)) then 0
else if (this:px(\$this)=this:px(\$prev)) then (
if (this:py(\$prev) > this:py(\$this))
then 270 (: remapped -90 :)
else 90
) else (
util:remap-degrees(util:degrees(
(math:pi() div 2) - math:atan2(this:px(\$this) - this:px(\$prev), this:py(\$this) - this:py(\$prev))
))
)
}```

#### `Function: inclinationdeclare function inclination(\$last as map(xs:string,item()*)?, \$curr as map(xs:string,item()*)) as xs:double`

inclination()
Compute the inclination angle from one point in the next, in degrees
Return 90 if the points are the same

##### Params
• last as map(xs:string,item()*)?: previous point; use (0,0,0) if no previous
• curr as map(xs:string,item()*): current point
##### Returns
• xs:double
```declare function this:inclination(\$last as map(xs:string,item()*)?, \$curr as map(xs:string,item()*)) as xs:double
{
let \$curr := \$curr=>this:as-dimension(3)
let \$prev := ((\$last,\$this:ORIGIN3D))=>this:as-dimension(3)
let \$d := this:distance(\$prev,\$curr)
return
if (this:same(\$prev,\$curr) or (\$d < \$config:ε)) then 90
else (
util:remap-degrees(util:degrees(
math:acos( (this:pz(\$curr) - this:pz(\$prev)) div \$d )
))
)
}```

#### ```Function: destinationdeclare function destination(\$point as map(xs:string,item()*), \$degrees as xs:double, \$length as xs:double) as map(xs:string,item()*)```

destination()
Point at a particular distance and direction.

##### Params
• point as map(xs:string,item()*): starting point
• degrees as xs:double: bearing from point
• length as xs:double: how far along bearing to travel
##### Returns
• map(xs:string,item()*)
```declare function this:destination(
\$point as map(xs:string,item()*),
\$degrees as xs:double,
\$length as xs:double
) as map(xs:string,item()*)
{
return
this:point(
this:px(\$point) + math:cos(\$angle)*\$length,
this:py(\$point) + math:sin(\$angle)*\$length
)
}```

#### ```Function: destinationdeclare function destination(\$point as map(xs:string,item()*), \$azimuth_degrees as xs:double, \$inclination_degrees as xs:double, \$length as xs:double) as map(xs:string,item()*)```

destination()
Point (3D) at a particular distance, azimuth, and inclination.

##### Params
• point as map(xs:string,item()*)
• azimuth_degrees as xs:double: angle of azimuth from point
• inclination_degrees as xs:double: angle of inclination from point
• length as xs:double: how far along bearing to travel
##### Returns
• map(xs:string,item()*)
```declare function this:destination(
\$point as map(xs:string,item()*),
\$azimuth_degrees as xs:double,
\$inclination_degrees as xs:double,
\$length as xs:double
) as map(xs:string,item()*)
{
(: Avoid having imprecision on sin/cos introduce non-zero Z values :)
let \$cos_inclination := math:cos(\$inclination)
let \$cos_inclination :=
if (abs(\$cos_inclination) < \$config:ε) then 0 else \$cos_inclination
let \$sin_inclination := math:sin(\$inclination)
let \$sin_inclination :=
if (abs(\$sin_inclination) < \$config:ε) then 0 else \$sin_inclination
return switch (this:dimension(\$point))
case 4 return
this:point(
this:px(\$point) + \$length*math:cos(\$azimuth)*\$sin_inclination,
this:py(\$point) + \$length*math:sin(\$azimuth)*\$sin_inclination,
this:pz(\$point) + \$length*\$cos_inclination,
this:pw(\$point)
)
default return
this:point(
this:px(\$point) + \$length*math:cos(\$azimuth)*\$sin_inclination,
this:py(\$point) + \$length*math:sin(\$azimuth)*\$sin_inclination,
this:pz(\$point) + \$length*\$cos_inclination
)
}```

#### ```Function: sorientationdeclare function sorientation(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*), \$r as map(xs:string,item()*)) as xs:double```

sorientation()
Orientation of triplet of 2D points p-q-r

0: PQ X PR = 0 colinear
1: PQ X PR > 0 R to left of PQ (counterclockwise)
-1: PQ X PR < 0 R to right of PQ (clockwise)

##### Params
• p as map(xs:string,item()*): one of the points (start of edge)
• q as map(xs:string,item()*): one of the points (end of edge)
• r as map(xs:string,item()*): one of the points (point relative to edge)
##### Returns
• xs:double: 0, 1, or -1 appropriately
```declare function this:sorientation(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$r as map(xs:string,item()*)
) as xs:double
{
util:zsign(
(this:px(\$q) - this:px(\$p)) * (this:py(\$r) - this:py(\$p)) -
(this:py(\$q) - this:py(\$p)) * (this:px(\$r) - this:px(\$p))
)
}```

#### ```Function: orientationdeclare function orientation(\$p as map(xs:string,item()*), \$q as map(xs:string,item()*), \$r as map(xs:string,item()*)) as xs:string```

orientation()
Orientation of triplet of 2D points p-q-r

"colinear" all on the same line
"clockwise" R to right of PQ
"counterclockwise" R to left of PQ

##### Params
• p as map(xs:string,item()*): one of the points (start of edge)
• q as map(xs:string,item()*): one of the points (end of edge)
• r as map(xs:string,item()*): one of the points (point relative to edge)
##### Returns
• xs:string
```declare function this:orientation(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$r as map(xs:string,item()*)
) as xs:string
{
let \$sign := this:sorientation(\$p, \$q, \$r)
return (
if (\$sign = 0) then "colinear"
else if (\$sign > 0) then "counterclockwise"
else "clockwise"
)
}```

#### `Function: centroiddeclare function centroid(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)`

centroid()
Compute center of mass of a sequence of points.

##### Params
• points as map(xs:string,item()*)*: sequence of points
##### Returns
• map(xs:string,item()*): point at center of points
```declare function this:centroid(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)
{
let \$maxd := this:max-dimension(\$points)
let \$n := count(\$points)
let \$coords :=
for \$d in 1 to \$maxd return (
sum(
for \$p in \$points return \$p=>this:pcoordinate(\$d)
) div \$n
)
return this:vector(\$coords)
}```

#### `Function: counterclockwisedeclare function counterclockwise(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)*`

counterclockwise()
Put points in counterclockwise order, suitable for a properly
normalized polygon.

##### Params
• points as map(xs:string,item()*)*: sequence of points
##### Returns
• map(xs:string,item()*)*: points in counterclockwise order
```declare function this:counterclockwise(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
let \$center := this:centroid(\$points)
let \$angles := \$points!this:angle(\$center, .)
let \$order := sort(1 to count(\$points), (), function (\$i as xs:integer) as xs:double {\$angles[\$i]})
return (
for \$i in \$order return \$points[\$i]
)
}```

#### ```Function: drawdeclare function draw(\$item as map(xs:string,item()*), \$properties as map(xs:string,item()*), \$drawing as map(xs:string,function(*)?)) as item()*```

##### Params
• item as map(xs:string,item()*)
• properties as map(xs:string,item()*)
• drawing as map(xs:string,function(*)?)
##### Returns
• item()*
```declare function this:draw(
\$item as map(xs:string,item()*),
\$properties as map(xs:string,item()*),
\$drawing as map(xs:string,function(*)?)
) as item()*
{
\$this:draw-impl(\$item, \$properties, \$drawing)
}```

### Original Source Code

```xquery version "3.1";
(:~
: Point objects; 2D and 3D and 4D
:
: Points have two kinds of accessors: "precision" accessors that return the
: raw double coordinates, and basic accessors which snap the coordinates
: to integer values.
:
: Points are maps of coordinates which may be decorated with other
: properties (e.g. for rendering). There are also methods for dealing with
: the point coordinates as sequences: these exist mainly for efficiency in
: certain cases (see noise modules and core/vector).
:
: @since March 2021
: @custom:Status Incomplete, subject to refactoring
:)
module namespace this="http://mathling.com/geometric/point";

import module namespace config="http://mathling.com/core/config"
at "../core/config.xqy";
import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy";
import module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy";

declare namespace art="http://mathling.com/art";
declare namespace svg="http://www.w3.org/2000/svg";
declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace array="http://www.w3.org/2005/xpath-functions/array";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";

declare variable \$this:ORIGIN as map(xs:string,item()*) := this:point(0,0);
declare variable \$this:ORIGIN2D as map(xs:string,item()*) := this:point(0,0);
declare variable \$this:ORIGIN3D as map(xs:string,item()*) := this:point(0,0,0);
declare variable \$this:ORIGIN4D as map(xs:string,item()*) := this:point(0,0,0,0);

declare variable \$this:RESERVED := ("kind", "x", "y", "z", "w", "dim");

(:======================================================================
: Constructors
:======================================================================:)

(:~
: point()
: Construct a 2D point
:
: @param \$x: x coordinate
: @param \$y: y coordinate
:)
declare function this:point(\$x as xs:double, \$y as xs:double) as map(xs:string,item()*)
{
map {
"kind": "point",
"x": \$x,
"y": \$y,
"dim": 2
}
};

(:~
: point()
: Construct a 3D point
:
: @param \$x: x coordinate
: @param \$y: y coordinate
: @param \$z: z coordinate
:)
declare function this:point(
\$x as xs:double,
\$y as xs:double,
\$z as xs:double
) as map(xs:string, item()*)
{
map {
"kind": "point",
"x": \$x,
"y": \$y,
"z": \$z,
"dim": 3
}
};

(:~
: point()
: Construct a 4D point
:
: @param \$x: x coordinate
: @param \$y: y coordinate
: @param \$z: z coordinate
: @param \$w: w coordinate
:)
declare function this:point(
\$x as xs:double,
\$y as xs:double,
\$z as xs:double,
\$w as xs:double
) as map(xs:string, item()*)
{
map {
"kind": "point",
"x": \$x,
"y": \$y,
"z": \$z,
"w": \$w,
"dim": 4
}
};

(:~
: vector()
: Construct a point of some dimension from a series of coordinates.
: Useful for genericizing some of the noise code. It is an error if the
: number of coordinates is not 2, 3, or 4. Coordinates are given in the
: (x, y, z w) order.
:
: @param \$coordinates: the coordinates
:)
declare function this:vector(
\$coordinates as xs:double*
) as map(xs:string,item()*)
{
switch (count(\$coordinates))
case 2 return this:point(\$coordinates, \$coordinates)
case 3 return this:point(\$coordinates, \$coordinates, \$coordinates)
case 4 return this:point(\$coordinates, \$coordinates, \$coordinates, \$coordinates)
default return errors:error("GEOM-UNKDIM", (count(\$coordinates), \$coordinates))
};

(:~
: as-dimension()
: Reconstitute a point as a point with the given number of dimensions.
: If we are reducing dimensions, just remove excess coordinates.
: If we are expanding dimensions, fill in with 0.
:
: Examples:
:   point:as-dimension(point:point(1,2), 3) == point:point(1,2,0)
:   point:as-dimension(point:point(1,2,3), 3) == point:point(1,2)
:
: @param \$point: source point
: @param \$dim: dimension to cast point to
:)
declare function this:as-dimension(
\$point as map(xs:string,item()*),
\$dim as xs:integer
) as map(xs:string,item()*)
{
let \$v := this:vector(this:pcoordinates(\$point, \$dim))
return
if (empty(this:properties(\$point))) then \$v
else util:merge-into(\$point, \$v)
};

(:======================================================================
: Accessors
:======================================================================:)

(:~
: dimension()
: Return the dimension of the point.
:
: @param \$point: the point
:)
declare function this:dimension(\$point as map(xs:string,item()*)) as xs:integer
{
(
\$point("dim"),
if (\$point=>map:contains("w")) then 4
else if (\$point=>map:contains("z")) then 3
else if (\$point=>map:contains("y")) then 2
else 0 (: Dunno :)
)
};

(:~
: max-dimension()
: Convenience method to return the maximum dimension of a set of points.
:
: @param \$points: sequence of points
:)
declare function this:max-dimension(\$points as map(xs:string,item()*)*) as xs:integer
{
max(for \$pt in \$points return this:dimension(\$pt))
};

(:~
: x()
: Accessor for the snapped x coordinate
:
: @param \$point: the point
:)
declare function this:x(\$point as map(xs:string,item()*)) as xs:integer
{
if (\$point("x") = xs:double("INF")) then \$util:UINT64_MAX
else if (\$point("x") = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$point("x")) cast as xs:integer
};

(:~
: y()
: Accessor for the snapped y coordinate
:
: @param \$point: the point
:)
declare function this:y(\$point as map(xs:string,item()*)) as xs:integer
{
if (\$point("y") = xs:double("INF")) then \$util:UINT64_MAX
else if (\$point("y") = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$point("y")) cast as xs:integer
};

(:~
: z()
: Accessor for the snapped z coordinate; return 0 for 2D point
:
: @param \$point: the point
:)
declare function this:z(\$point as map(xs:string,item()*)) as xs:integer
{
return
if (\$z = xs:double("INF")) then \$util:UINT64_MAX
else if (\$z = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$z) cast as xs:integer
};

(:~
: w()
: Accessor for the snapped w coordinate; return 0 for 2D or 3D point
:
: @param \$point: the point
:)
declare function this:w(\$point as map(xs:string,item()*)) as xs:integer
{
return
if (\$w = xs:double("INF")) then \$util:UINT64_MAX
else if (\$w = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$w) cast as xs:integer
};

(:~
: px()
: Accessor for the raw precision x coordinate
:
: @param \$point: the point
:)
declare function this:px(\$point as map(xs:string,item()*)) as xs:double
{
\$point("x")
};

(:~
: py()
: Accessor for the raw precision y coordinate
:
: @param \$point: the point
:)
declare function this:py(\$point as map(xs:string,item()*)) as xs:double
{
\$point("y")
};

(:~
: pz()
: Accessor for the raw precision z coordinate; returns 0 for 2D point
:
: @param \$point: the point
:)
declare function this:pz(\$point as map(xs:string,item()*)) as xs:double
{
};

(:~
: pw()
: Accessor for the raw precision w coordinate; returns 0 for 2D or 3D point
:
: @param \$point: the point
:)
declare function this:pw(\$point as map(xs:string,item()*)) as xs:double
{
};

(:~
: coordinates()
: Return snapped coordinates of the point as a sequence.
:
: @param \$point: the point
:)
declare function this:coordinates(\$point as map(xs:string,item()*)) as xs:integer*
{
let \$d := this:dimension(\$point)
return (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:x(\$point), this:y(\$point)) else (),
if (\$d >= 3) then this:z(\$point) else (),
if (\$d >= 4) then this:w(\$point) else ()
)
};

(:~
: coordinates()
: Return snapped coordinates of the point as a sequence, treating the
: point as having a given dimension.
:
: @param \$point: the point
: @param \$d: desired dimension
:)
declare function this:coordinates(\$point as map(xs:string,item()*), \$d as xs:integer) as xs:integer*
{
if (\$d = 0) then this:coordinates(\$point)
else (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:x(\$point), this:y(\$point)) else (),
if (\$d >= 3) then this:z(\$point) else (),
if (\$d >= 4) then this:w(\$point) else ()
)
};

(:~
: pcoordinates()
: Return raw precision coordinates of the point as a sequence.
:
: @param \$point: the point
:)
declare function this:pcoordinates(\$point as map(xs:string,item()*)) as xs:double*
{
let \$d := this:dimension(\$point)
return (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:px(\$point), this:py(\$point)) else (),
if (\$d >= 3) then this:pz(\$point) else (),
if (\$d >= 4) then this:pw(\$point) else ()
)
};

(:~
: pcoordinates()
: Return raw precision coordinates of the point as a sequence, treating the
: point as having a given dimension.
:
: @param \$point: the point
: @param \$d: the desired dimension
:)
declare function this:pcoordinates(\$point as map(xs:string,item()*), \$d as xs:integer) as xs:double*
{
if (\$d = 0) then this:pcoordinates(\$point)
else (
if (\$d < 2 or \$d > 4)
then errors:error("GEOM-UNKDIM", (\$d, \$point))
else (),
if (\$d >= 2) then (this:px(\$point), this:py(\$point)) else (),
if (\$d >= 3) then this:pz(\$point) else (),
if (\$d >= 4) then this:pw(\$point) else ()
)
};

(:~
: pcoordinate()
: Return the given raw precision coordinates of the point.
:
: @param \$point: the point
: @param \$ix: index of the coordinate, e.g. 1=1st, etc.
:)
declare function this:pcoordinate(\$point as map(xs:string,item()*), \$ix as xs:integer) as xs:double
{
switch (\$ix)
case 1 return this:px(\$point)
case 2 return this:py(\$point)
case 3 return this:pz(\$point)
case 4 return this:pw(\$point)
default return errors:error("GEOM-UNKDIM", (\$ix, \$point))
};

(:~
: kind()
: Accessor for the kind of data this is; for debugging and polymorphic geo
: functions
:
: @param \$point: the point
:)
declare function this:kind(\$point as map(xs:string,item()*)) as xs:string
{
\$point("kind")
};

(:======================================================================:
: Property management
:======================================================================:)

(:~
: property-map()
: Return the annotation properties of the point as a map. Check whether this
: is actually a point.
:
: @param \$region: the region
:)
declare function this:property-map(\$region as map(xs:string,item()*)) as map(xs:string, item()*)
{
switch(this:kind(\$region))
case "point" return util:exclude(\$region, \$this:RESERVED)
default return map {}
};

(:~
: properties()
: Return the names of the annotation properties of the point.
: Check whether this is actually a point.
:
: @param \$region: the region
:)
declare function this:properties(\$region as map(xs:string,item()*)) as xs:string*
{
switch(this:kind(\$region))
case "point" return (\$region=>map:keys())[not(. = \$this:RESERVED)]
default return ()
};

(:~
: with-properties()
: Annotate the point with some new properties and return the new point.
: Will not touch any of the core properties. Will override existing properties
: with the same keys but leave properties with different keys in place.
: Raises an error if this is not actually a point.
:
: @param \$region: the region
: @param \$properties: the properties
:)
declare function this:with-properties(
\$region as map(xs:string,item()*),
\$properties as map(xs:string, item()*)
) as map(xs:string, item()*)
{
switch(this:kind(\$region))
case "point" return
util:merge-into(\$region, util:exclude(\$properties,\$this:RESERVED))
};

(:======================================================================:
: Point operations
:======================================================================:)

(:~
: midpoint()
: Return midpoint between two points
:
: @param \$a: one point
: @param \$b: the other
:)
declare function this:midpoint(\$a as map(xs:string,item()*), \$b as map(xs:string,item()*)) as map(xs:string,item()*)
{
this:map2(
function (\$ca as xs:double, \$cb as xs:double) as xs:double {
(\$ca + \$cb) div 2
},
\$a,
\$b
)
};

(:~
: magnitude()
: Magnitude of the point as vector from origin.
:
: @param \$v: the point
:)
declare function this:magnitude(\$v as map(xs:string,item()*)) as xs:double
{
math:sqrt( this:dot2(\$v) )
};

(:~
: dot()
: Compute the dot product of two points
:
: @param \$p1: one point
: @param \$p2: another point
:)
declare function this:dot(
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p1, \$p2))
return (
sum(
util:zip(
function (\$c1 as xs:double, \$c2 as xs:double) as xs:double {
\$c1*\$c2
},
this:pcoordinates(\$p1, \$d),
this:pcoordinates(\$p2, \$d)
)
)
)
};

(:~
: dot2()
: Compute the dot product of a point with itself
:
: @param \$p: the point
:)
declare function this:dot2(
\$p as map(xs:string,item()*)
) as xs:double
{
sum(
for-each(this:pcoordinates(\$p), function (\$c as xs:double) as xs:double { \$c*\$c })
)
};

(:~
: determinant()
: Compute the determinant of two 2D points.
:
: @param \$u: one point
: @param \$v: another point
:)
declare function this:determinant(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as xs:double
{
\$u=>this:px()*\$v=>this:py() - \$u=>this:py()*\$v=>this:px()
};

(:~
: cross()
: Compute the cross product of two 3D points.
:
: @param \$u: one point
: @param \$v: another point
:)
declare function this:cross(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
this:point(
\$u=>this:py()*\$v=>this:pz() - \$u=>this:pz()*\$v=>this:py(),
\$u=>this:pz()*\$v=>this:px() - \$u=>this:px()*\$v=>this:pz(),
\$u=>this:px()*\$v=>this:py() - \$u=>this:py()*\$v=>this:px()
)
};

(:~
: times()
: Scale every coordinate by a constant, returning the new point.
:
: @param \$u: point to scale
: @param \$k: constant multiplier
:)
declare function this:times(
\$u as map(xs:string,item()*),
\$k as xs:double
) as map(xs:string,item()*)
{
this:map(function (\$c as xs:double) as xs:double {\$k * \$c}, \$u)
};

(:~
: Add two points, returning the new point.
:
: @param \$u: one point
: @param \$v: the other point
:)
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
this:map2(function (\$a as xs:double, \$b as xs:double) as xs:double {\$a + \$b}, \$u, \$v)
};

(:~
: sum()
: Add a series of points, returning the new point.
:
: @param \$pts: the points
: @param \$v: the other point
:)
declare function this:sum(
\$pts as map(xs:string,item()*)*
) as map(xs:string,item()*)
{
this:vector(
for \$d in 1 to this:max-dimension(\$pts) return (
sum(\$pts!this:pcoordinate(., \$d))
)
)
};

(:~
: sub()
: Subtract one point from another, returning the new point.
:
: @param \$u: one point
: @param \$v: the other point
:)
declare function this:sub(
\$u as map(xs:string,item()*),
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
this:map2(function (\$a as xs:double, \$b as xs:double) as xs:double {\$a - \$b}, \$u, \$v)
};

(:~
: minus()
: Negate the coordinates of the point, returning the new point.
:
: @param \$v: the point
:)
declare function this:minus(\$v as map(xs:string,item()*))
as map(xs:string,item()*)
{
this:map(function (\$a as xs:double) as xs:double {-\$a}, \$v)
};

(:~
: normalize()
: Normalize a vector represented as a point (scale to unit vector), returning
: the new point.
:
: @param \$v: the point
:)
declare function this:normalize(
\$v as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$l := this:magnitude(\$v)
return
if (\$l = 0) then \$v
else this:map(function (\$c as xs:double) as xs:double {\$c div \$l}, \$v)
};

(:~
: perpendicular()
: Compute the perpendicular vector for a 2D vector represented as a point.
: (Rotated by 90 degrees.)
:
: @param \$v: the point
:)
declare function this:perpendicular(
\$v as map(xs:string, item()*)
) as map(xs:string, item()*)
{
this:point(\$v=>this:py(), -(\$v=>this:px()))
};

(:~
: perpendiculars()
: Compute both perpendicular vectors for a 2D vector represented as a point.
: Rotated by 90 degrees and by -90 degrees, in that order.
:
: @param \$v: the point
:)
declare function this:perpendiculars(\$v as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:point(this:py(\$v), -this:px(\$v)),
this:point(-this:py(\$v), this:px(\$v))
};

(:~
: perpendiculars-with-z()
: Compute perpendicular vectors for a 3D vector represented as a point.
: Rotated in y by 90 degrees and by -90 degrees, in that order, with z
: rotated in the corresponding direction. For spline/smoothing.
:
: @param \$v: the point
:)
declare function this:perpendiculars-with-z(\$v as map(xs:string,item()*)) as map(xs:string,item()*)*
{
this:point(this:py(\$v), -this:px(\$v), this:pz(\$v)),
this:point(-this:py(\$v), this:px(\$v), -this:pz(\$v))
};

(:~
: snap()
: Snap the coordinates of the points, returning the points with snapped
: (i.e. integer) coordinates. NaN snaps to UINT_MAX
:
: @param \$points: the points
:)
declare function this:snap(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
for \$point in \$points return (
this:map(
function (\$c as xs:double) as xs:integer {
if (\$c = xs:double("INF") or not(\$c = \$c)) then \$util:UINT64_MAX
else if (\$c = xs:double("-INF")) then -\$util:UINT64_MAX
else round-half-to-even(\$c) cast as xs:integer
},
\$point
)
)
};

(:~
: floor()
: Return points where all the coordinates have been set to the floor of
: the original points' values. Differs from snap() for negative coordinates.
:
: @param \$points: the points
:)
declare function this:floor(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
for \$point in \$points return (
this:map(fn:floor#1, \$point)
)
};

(:~
: decimal()
: Perform decimal rounding on all the coordinates (see util:decimal).
:
: @param \$points: the points to round
: @param \$digits: how many digits after the decimal point to keep
:)
declare function this:decimal(
\$points as map(xs:string,item()*)*,
\$digits as xs:integer
) as map(xs:string,item()*)*
{
for \$point in \$points return (
this:map(function (\$c as xs:double) as xs:double {util:decimal(\$c, \$digits)}, \$point)
)
};

(:~
: quote()
: Return a string value for the points, suitable for debugging.
:
: @param \$points: the point sequence to quote
:)
declare function this:quote(\$points as map(xs:string,item()*)*) as xs:string
{
string-join(
for \$point in \$points return (
string-join(this:pcoordinates(\$point)!string(.),",")
)
,
" "
)
};

(:~
: valid()
: False if a coordinate is INF or NaN
:)
declare function this:valid(\$point as map(xs:string,item()*)) as xs:boolean
{
every \$c in this:pcoordinates(\$point)
satisfies \$c=\$c and not(\$c=(xs:double("INF"),xs:double("-INF")))
};

(:~
: same()
: Equality comparison for points, ignoring annotation properties.
: Return true() if they have equal coordinates.
:
: @param \$this: one point
: @param \$other: the point to compare it to
:)
declare function this:same(
\$this as map(xs:string,item()*),
\$other as map(xs:string,item()*)
) as xs:boolean
{
let \$d := this:max-dimension((\$this, \$other))
return (
deep-equal(
this:pcoordinates(\$this, \$d),
this:pcoordinates(\$other, \$d)
)
)
};

(:~
: same()
: Equality comparison for points, ignoring annotation properties.
: Return true() if they have equal coordinates within the given tolerance.
:
: @param \$this: one point
: @param \$other: the point to compare it to
: @param \$tolerance: allowable slop
:)
declare function this:same(
\$this as map(xs:string,item()*),
\$other as map(xs:string,item()*),
\$tolerance as xs:double
) as xs:boolean
{
let \$d := this:max-dimension((\$this, \$other))
return (
util:every(
util:zip(
function (\$c1 as xs:double, \$c2 as xs:double) as xs:boolean {
abs(\$c1 - \$c2) < \$tolerance
},
this:pcoordinates(\$this, \$d),
this:pcoordinates(\$other, \$d)
)
)
)
};

(:~
: mutate()
: Run a function over a sequence of points to produce a new sequence of
: points.
:
: @param \$regions: input sequence of points
: @param \$mutate: function that takes a point as an argument and returns a new point
:)
declare function this:mutate(
\$regions as map(xs:string,item()*)*,
\$mutate as function (map(xs:string,item()*)) as map(xs:string,item()*)
) as map(xs:string,item()*)*
{
\$regions!\$mutate(.)
};

(:~
: distinct()
: Return the points in the sequence that have distinct coordinates from
: each other.
:
: @param \$points: the sequence of points
:)
declare function this:distinct(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
if (empty(\$points)) then () else
let \$max-d := this:max-dimension(\$points)
let \$sorted :=
sort(\$points, (), function (\$pt as map(xs:string,item()*)) as xs:double* {this:pcoordinates(\$pt, \$max-d)})
return (
for \$pt at \$i in tail(\$sorted)
where not(this:same(\$pt, \$sorted[\$i]))
return \$pt
)
};

(:~
: unquote()
: Parse a string representation of a point and produce a point.
: The string consists of the coordinates in x,y,z,w order with commas and
: optional spaces in between.
: Example "3.4, 1" == point:point(3.4, 1)
:
: @param \$string: the input string to parse
:)
declare function this:unquote(
\$string as xs:string
) as map(xs:string,item()*)
{
this:vector(tokenize(\$string, "[ ]*,[ ]*")!xs:double(.))
};

(:~
: map()
: Map a function over the coordinates of a point, returning the new point.
:
: @param \$f: function from coordinate to coordinate
: @param \$point: input point
:)
declare function this:map(
\$f as function(xs:double) as xs:double,
\$point as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$v := this:vector(fn:for-each(this:pcoordinates(\$point), \$f))
return (
if (empty(this:properties(\$point))) then \$v
else util:merge-into(\$point, \$v)
)
};

(:~
: map()
: Map a function over the coordinates of a point, returning the new point.
: Treat the point as having a particular dimension.
:
: @param \$f: function from coordinate to coordinate
: @param \$point: input point
: @param \$d: the effective dimension of the point
:)
declare function this:map(
\$f as function(xs:double) as xs:double,
\$point as map(xs:string,item()*),
\$d as xs:integer
) as map(xs:string,item()*)
{
let \$v := this:vector(fn:for-each(this:pcoordinates(\$point, \$d), \$f))
return (
if (empty(this:properties(\$point))) then \$v
else util:merge-into(\$point, \$v)
)
};

(:~
: map2()
: Map a function over the coordinates of two points, pair by pair, returning
: the new point. That is, apply function to the x coordinates, then the
: y coordinates, and so on.
:
: @param \$f: function from coordinate pairs to coordinate
: @param \$p1: one input point
: @param \$p2: another input point
:)
declare function this:map2(
\$f as function(xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*)
) as map(xs:string,item()*)
{
let \$d := this:max-dimension((\$p1, \$p2))
return
this:vector(
util:zip(\$f, this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d))
)
};

(:~
: map2()
: Map a function over the coordinates of two points, pair by pair, returning
: the new point. That is, apply function to the x coordinates, then the
: y coordinates, and so on. Treat the point as having a particular dimension.
:
: @param \$f: function from coordinate pairs to coordinate
: @param \$p1: one input point
: @param \$p2: another input point
: @param \$d: the effective dimension of the points
:)
declare function this:map2(
\$f as function(xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$d as xs:integer
) as map(xs:string,item()*)
{
this:vector(
util:zip(\$f, this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d))
)
};

(:~
: map3()
: Map a function over the coordinates of three points, triple by triple,
: returning the new point. That is, apply function to the x coordinates,
: then the y coordinates, and so on.
:
: @param \$f: function from coordinate pairs to coordinate
: @param \$p1: one input point
: @param \$p2: another input point
: @param \$p3: third input point
:)
declare function this:map3(
\$f as function(xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*)
) as map(xs:string, item()*)
{
let \$d := this:max-dimension((\$p1, \$p2, \$p3))
return
this:vector(
util:zip(
function (\$a as xs:double, \$fc as function(xs:double) as xs:double) {\$fc(\$a)},
this:pcoordinates(\$p3, \$d),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?) },
this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d)
)
)
)
};

(:~
: map3()
: Map a function over the coordinates of three points, triple by triple,
: returning the new point. That is, apply function to the x coordinates,
: then the y coordinates, and so on. Treat the point as having a
: particular dimension.
:
: @param \$f: function from coordinate pairs to coordinate
: @param \$p1: one input point
: @param \$p2: another input point
: @param \$p3: third input point
: @param \$d: the effective dimension of the points
:)
declare function this:map3(
\$f as function(xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*),
\$d as xs:integer
) as map(xs:string, item()*)
{
this:vector(
util:zip(
function (\$a as xs:double, \$fc as function(xs:double) as xs:double) as xs:double {\$fc(\$a)},
this:pcoordinates(\$p3, \$d),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?) },
this:pcoordinates(\$p1, \$d), this:pcoordinates(\$p2, \$d)
)
)
)
};

(:~
: map4()
: Map a function over the coordinates of four points, coordinate by
: coordinate, returning the new point. That is, apply function to the x
: coordinates, then the y coordinates, and so on.
:
: @param \$f: function from coordinate pairs to coordinate
: @param \$p1: one input point
: @param \$p2: another input point
: @param \$p3: third input point
: @param \$p4: fourth input point
:)
declare function this:map4(
\$f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*),
\$p4 as map(xs:string,item()*)
) as map(xs:string, item()*)
{
let \$dim := this:max-dimension((\$p1, \$p2, \$p3))
return
this:vector(
util:zip(
function (\$d as xs:double, \$fd as function(xs:double) as xs:double) as xs:double {\$fd(\$d)},
this:pcoordinates(\$p4, \$dim),
util:zip(
function (\$c as xs:double, \$fc as function(xs:double, xs:double) as xs:double) as function(*) {\$fc(\$c, ?)},
this:pcoordinates(\$p3, \$dim),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?, ?) },
this:pcoordinates(\$p1, \$dim), this:pcoordinates(\$p2, \$dim)
)
)
)
)
};

(:~
: map4()
: Map a function over the coordinates of four points, coordinate by
: coordinate, returning the new point. That is, apply function to the x
: coordinates, then the y coordinates, and so on. Treat the point as having a
: particular dimension.
:
: @param \$f: function from coordinate pairs to coordinate
: @param \$p1: one input point
: @param \$p2: another input point
: @param \$p3: third input point
: @param \$p4: fourth input point
: @param \$d: the effective dimension of the points
:)
declare function this:map4(
\$f as function(xs:double, xs:double, xs:double, xs:double) as xs:double,
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*),
\$p3 as map(xs:string,item()*),
\$p4 as map(xs:string,item()*),
\$dim as xs:integer
) as map(xs:string, item()*)
{
this:vector(
util:zip(
function (\$d as xs:double, \$fd as function(xs:double) as xs:double) as xs:double {\$fd(\$d)},
this:pcoordinates(\$p4, \$dim),
util:zip(
function (\$c as xs:double, \$fc as function(xs:double, xs:double) as xs:double) as function(*) {\$fc(\$c, ?)},
this:pcoordinates(\$p3, \$dim),
util:zip(
function (\$a as xs:double, \$b as xs:double) as function(*) { \$f(\$a, \$b, ?, ?) },
this:pcoordinates(\$p1, \$dim), this:pcoordinates(\$p2, \$dim)
)
)
)
)
};

(:~
: distance()
: Euclidean distance between points
:)
declare function this:distance(
\$p1 as map(xs:string,item()*),
\$p2 as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p1, \$p2))
return (
math:sqrt(
sum(
util:zip(
function (\$c1 as xs:double, \$c2 as xs:double) as xs:double {
(\$c1 - \$c2)*(\$c1 - \$c2)
},
this:pcoordinates(\$p1, \$d),
this:pcoordinates(\$p2, \$d)
)
)
)
)
};

(:~
: polar-distance()
: Angular distance between points taken as vectors.
:)
declare function this:polar-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
abs(this:angle(\$this:ORIGIN2D, \$p) - this:angle(\$this:ORIGIN2D, \$q))
};

(:~
: taxi-distance()
: Taxi-cab distance between two points. That is, right angle distance, such as
: a taxi going 2 blocks North plus 3 blocks East = distance 5 blocks.
:)
declare function this:taxi-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
sum(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
abs(\$cp - \$cq)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
)
)
};

(:~
: avg-distance()
: Average of distances along each dimension.
:)
declare function this:avg-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
avg(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
abs(\$cp - \$cq)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
)
)
};

(:~
: rail-distance()
: Railway distance, e.g distance to the central hub and back out.
: The hub is the origin.
:)
declare function this:rail-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
this:distance(\$p, \$this:ORIGIN) + this:distance(\$q, \$this:ORIGIN)
};

(:~
: rail-distance()
: Railway distance, e.g distance to the central hub and back out where the hub
: is specified.
:)
declare function this:rail-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$origin as map(xs:string,item()*)
) as xs:double
{
this:distance(\$p, \$origin) + this:distance(\$q, \$origin)
};

(:~
: chebyshev-distance()
: Chebyshev distance metric. The maximum of the dimensional distances.
:)
declare function this:chebyshev-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
max(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
abs(\$cp - \$cq)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
)
)
};

(:~
: minkokski-distance()
: Minkowski distance metric of the given order. Certain orders give rise to
: other metrics:
: order=1=>taxi; order=2=>Euclidean; as order=>∞=>Chebyshev;
: order<1 is not a metric unless undo the final pow. i.e. X is a metric but
:   X^(1/order) is not.
:)
declare function this:minkowski-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$order as xs:double
) as xs:double
{
let \$d := this:max-dimension((\$p, \$q))
return (
math:pow(
sum(
util:zip(
function (\$cp as xs:double, \$cq as xs:double) as xs:double {
math:pow(abs(\$cp - \$cq), \$order)
},
this:pcoordinates(\$p, \$d),
this:pcoordinates(\$q, \$d)
)
),
1 div \$order
)
)
};

(:~
: cosine-similarity()
: Cosine similarity: (A·B)/(∥A∥∥B∥) = ΣAiBi/(√Ai²√Bi²)
:)
declare function this:cosine-similarity(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
this:dot(\$p, \$q) div (this:magnitude(\$p)*this:magnitude(\$q))
};

(:~
: cosine-distance()
: Cosine similarity as a metric: acos(similarity)/π => [0,1]
:)
declare function this:cosine-distance(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*)
) as xs:double
{
math:acos(this:cosine-similarity(\$p, \$q)) div math:pi()
};

(:~
: angle()
: Compute the angle (azimuth) from one point to the next, in degrees
: Return 0 if points are the same
:
: @param \$last: previous point; use (0,0) if no previous
: @param \$curr: current point
:)
declare function this:angle(\$last as map(xs:string,item()*)?, \$curr as map(xs:string,item()*)) as xs:double
{
let \$this := \$curr=>this:as-dimension(2)
let \$prev := this:as-dimension((\$last,\$this:ORIGIN),2)
return
if (this:same(\$prev, \$this) or (this:distance(\$prev,\$this) < \$config:ε)) then 0
else if (this:px(\$this)=this:px(\$prev)) then (
if (this:py(\$prev) > this:py(\$this))
then 270 (: remapped -90 :)
else 90
) else (
util:remap-degrees(util:degrees(
(math:pi() div 2) - math:atan2(this:px(\$this) - this:px(\$prev), this:py(\$this) - this:py(\$prev))
))
)
};

(:~
: inclination()
: Compute the inclination angle from one point in the next, in degrees
: Return 90 if the points are the same
:
: @param \$last: previous point; use (0,0,0) if no previous
: @param \$curr: current point
:)
declare function this:inclination(\$last as map(xs:string,item()*)?, \$curr as map(xs:string,item()*)) as xs:double
{
let \$curr := \$curr=>this:as-dimension(3)
let \$prev := ((\$last,\$this:ORIGIN3D))=>this:as-dimension(3)
let \$d := this:distance(\$prev,\$curr)
return
if (this:same(\$prev,\$curr) or (\$d < \$config:ε)) then 90
else (
util:remap-degrees(util:degrees(
math:acos( (this:pz(\$curr) - this:pz(\$prev)) div \$d )
))
)
};

(:~
: destination()
: Point at a particular distance and direction.
:
: @param \$point: starting point
: @param \$degrees: bearing from point
: @param \$length: how far along bearing to travel
:)
declare function this:destination(
\$point as map(xs:string,item()*),
\$degrees as xs:double,
\$length as xs:double
) as map(xs:string,item()*)
{
return
this:point(
this:px(\$point) + math:cos(\$angle)*\$length,
this:py(\$point) + math:sin(\$angle)*\$length
)
};

(:~
: destination()
: Point (3D) at a particular distance, azimuth, and inclination.
:
: @param \$this: starting point
: @param \$azimuth_degrees: angle of azimuth from point
: @param \$inclination_degrees: angle of inclination from point
: @param \$length: how far along bearing to travel
:)
declare function this:destination(
\$point as map(xs:string,item()*),
\$azimuth_degrees as xs:double,
\$inclination_degrees as xs:double,
\$length as xs:double
) as map(xs:string,item()*)
{
(: Avoid having imprecision on sin/cos introduce non-zero Z values :)
let \$cos_inclination := math:cos(\$inclination)
let \$cos_inclination :=
if (abs(\$cos_inclination) < \$config:ε) then 0 else \$cos_inclination
let \$sin_inclination := math:sin(\$inclination)
let \$sin_inclination :=
if (abs(\$sin_inclination) < \$config:ε) then 0 else \$sin_inclination
return switch (this:dimension(\$point))
case 4 return
this:point(
this:px(\$point) + \$length*math:cos(\$azimuth)*\$sin_inclination,
this:py(\$point) + \$length*math:sin(\$azimuth)*\$sin_inclination,
this:pz(\$point) + \$length*\$cos_inclination,
this:pw(\$point)
)
default return
this:point(
this:px(\$point) + \$length*math:cos(\$azimuth)*\$sin_inclination,
this:py(\$point) + \$length*math:sin(\$azimuth)*\$sin_inclination,
this:pz(\$point) + \$length*\$cos_inclination
)
};

(:~
: sorientation()
: Orientation of triplet of 2D points p-q-r
:
: 0: PQ X PR = 0  colinear
: 1: PQ X PR > 0  R to left of PQ (counterclockwise)
: -1: PQ X PR < 0  R to right of PQ (clockwise)
:
: @param \$p: one of the points (start of edge)
: @param \$q: one of the points (end of edge)
: @param \$r: one of the points (point relative to edge)
: @return 0, 1, or -1 appropriately
:)
declare function this:sorientation(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$r as map(xs:string,item()*)
) as xs:double
{
util:zsign(
(this:px(\$q) - this:px(\$p)) * (this:py(\$r) - this:py(\$p)) -
(this:py(\$q) - this:py(\$p)) * (this:px(\$r) - this:px(\$p))
)
};

(:~
: orientation()
: Orientation of triplet of 2D points p-q-r
:
: "colinear"          all on the same line
: "clockwise"         R to right of PQ
: "counterclockwise"  R to left of PQ
:
: @param \$p: one of the points (start of edge)
: @param \$q: one of the points (end of edge)
: @param \$r: one of the points (point relative to edge)
:)
declare function this:orientation(
\$p as map(xs:string,item()*),
\$q as map(xs:string,item()*),
\$r as map(xs:string,item()*)
) as xs:string
{
let \$sign := this:sorientation(\$p, \$q, \$r)
return (
if (\$sign = 0) then "colinear"
else if (\$sign > 0) then "counterclockwise"
else "clockwise"
)
};

(:~
: centroid()
: Compute center of mass of a sequence of points.
:
: @param \$points: sequence of points
: @return point at center of points
:)
declare function this:centroid(\$points as map(xs:string,item()*)*) as map(xs:string,item()*)
{
let \$maxd := this:max-dimension(\$points)
let \$n := count(\$points)
let \$coords :=
for \$d in 1 to \$maxd return (
sum(
for \$p in \$points return \$p=>this:pcoordinate(\$d)
) div \$n
)
return this:vector(\$coords)
};

(:~
: counterclockwise()
: Put points in counterclockwise order, suitable for a properly
: normalized polygon.
:
: @param \$points: sequence of points
: @return points in counterclockwise order
:)
declare function this:counterclockwise(
\$points as map(xs:string,item()*)*
) as map(xs:string,item()*)*
{
let \$center := this:centroid(\$points)
let \$angles := \$points!this:angle(\$center, .)
let \$order := sort(1 to count(\$points), (), function (\$i as xs:integer) as xs:double {\$angles[\$i]})
return (
for \$i in \$order return \$points[\$i]
)
};

declare %private variable \$this:draw-impl as function(*) :=
if (\$config:DRAWING-METHOD="art") then (
if (\$config:DRAW-SNAPPED) then (
function(
\$item as map(xs:string,item()*),
\$properties as map(xs:string,item()*),
\$drawing as map(xs:string,function(*)?)
) as item()* {
let \$style-properties := util:merge-into((\$properties, this:property-map(\$item)))
return (
if (\$config:POINT-IS-PIXEL) then (
<art:px x="{this:x(\$item)}" y="{this:y(\$item)}">{
util:as-attributes(\$style-properties)
}</art:px>
) else (
<art:dot x="{this:x(\$item)}" y="{this:y(\$item)}">{
util:as-attributes(\$style-properties)
}</art:dot>
)
)
}
) else (
function(
\$item as map(xs:string,item()*),
\$properties as map(xs:string,item()*),
\$drawing as map(xs:string,function(*)?)
) as item()* {
let \$style-properties := util:merge-into((\$properties, this:property-map(\$item)))
return (
if (\$config:POINT-IS-PIXEL) then (
<art:px x="{this:px(\$item)}" y="{this:py(\$item)}">{
util:as-attributes(\$style-properties)
}</art:px>
) else (
<art:dot x="{this:px(\$item)}" y="{this:py(\$item)}">{
util:as-attributes(\$style-properties)
}</art:dot>
)
)
}
)
) else (
if (\$config:DRAW-SNAPPED) then (
function(
\$item as map(xs:string,item()*),
\$properties as map(xs:string,item()*),
\$drawing as map(xs:string,function(*)?)
) as item()* {
let \$style-properties :=
util:merge-into((\$drawing("draw:svg-style")(\$properties), \$drawing("draw:svg-style")(this:property-map(\$item))))
return (
if (\$config:POINT-IS-PIXEL) then (
let \$size :=
if (exists(\$style-properties("r"))) then \$style-properties("r") else 1
let \$x := this:x(\$item)
let \$y := this:y(\$item)
return (
<svg:polyline points="{\$x},{\$y} {\$x+\$size},{\$y} {\$x+\$size},{\$y+\$size} {\$x},{\$y+1} {\$x},{\$y}"
>{
if (exists(\$style-properties("stroke-width"))) then () else (
attribute stroke-width {0.1}
),
util:as-attributes(util:exclude(\$style-properties,("r")))
}</svg:polyline>
)
) else (
<svg:circle cx="{this:x(\$item)}" cy="{this:y(\$item)}">{
if (exists(\$style-properties("r"))) then () else (
attribute r {3}
),
util:as-attributes(\$style-properties)
}</svg:circle>
)
)
}
) else (
function(
\$item as map(xs:string,item()*),
\$properties as map(xs:string,item()*),
\$drawing as map(xs:string,function(*)?)
) as item()* {
let \$style-properties :=
util:merge-into((\$drawing("draw:svg-style")(\$properties), \$drawing("draw:svg-style")(this:property-map(\$item))))
return (
if (\$config:POINT-IS-PIXEL) then (
let \$size :=
if (exists(\$style-properties("r"))) then \$style-properties("r") else 1
let \$x := this:px(\$item)
let \$y := this:py(\$item)
return (
<svg:polyline points="{\$x},{\$y} {\$x+\$size},{\$y} {\$x+\$size},{\$y+\$size} {\$x},{\$y+1} {\$x},{\$y}"
>{
if (exists(\$style-properties("stroke-width"))) then () else (
attribute stroke-width {0.1}
),
util:as-attributes(util:exclude(\$style-properties,("r")))
}</svg:polyline>
)
) else (
<svg:circle cx="{this:px(\$item)}" cy="{this:py(\$item)}">{
if (exists(\$style-properties("r"))) then () else (
attribute r {3}
),
util:as-attributes(\$style-properties)
}</svg:circle>
)
)
}
)
)
;

declare function this:draw(
\$item as map(xs:string,item()*),
\$properties as map(xs:string,item()*),
\$drawing as map(xs:string,function(*)?)
) as item()*
{
\$this:draw-impl(\$item, \$properties, \$drawing)
};```