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).
Copyright© Mary Holstege 2020-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)
Status: Incomplete, subject to refactoring
Imports
http://mathling.com/core/utilitiesimport 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"
Variables
Variable: $ORIGIN as map(xs:string,item()*)
Variable: $ORIGIN2D as map(xs:string,item()*)
Variable: $ORIGIN3D as map(xs:string,item()*)
Variable: $ORIGIN4D as map(xs:string,item()*)
Variable: $RESERVED as
Functions
Function: point
declare function point($x as xs:double, $y as xs:double) as map(xs:string,item()*)
declare 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: point
declare function point($x as xs:double,
$y as xs:double,
$z as xs:double) as map(xs:string, item()*)
declare 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: point
declare function point($x as xs:double,
$y as xs:double,
$z as xs:double,
$w as xs:double) as map(xs:string, item()*)
declare 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: vector
declare function vector($coordinates as xs:double*) as map(xs:string,item()*)
declare 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[1], $coordinates[2]) case 3 return this:point($coordinates[1], $coordinates[2], $coordinates[3]) case 4 return this:point($coordinates[1], $coordinates[2], $coordinates[3], $coordinates[4]) default return errors:error("GEOM-UNKDIM", (count($coordinates), $coordinates)) }
Function: as-dimension
declare function as-dimension($point as map(xs:string,item()*),
$dim as xs:integer) as map(xs:string,item()*)
declare 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: dimension
declare function dimension($point as map(xs:string,item()*)) as xs:integer
declare 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 :) )[1] }
Function: max-dimension
declare function max-dimension($points as map(xs:string,item()*)*) as xs:integer
declare 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: x
declare function x($point as map(xs:string,item()*)) as xs:integer
declare 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: y
declare function y($point as map(xs:string,item()*)) as xs:integer
declare 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: z
declare function z($point as map(xs:string,item()*)) as xs:integer
declare 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 { let $z := head(($point("z"),0)) 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: w
declare function w($point as map(xs:string,item()*)) as xs:integer
declare 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 { let $w := head(($point("w"),0)) 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: px
declare function px($point as map(xs:string,item()*)) as xs:double
declare 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: py
declare function py($point as map(xs:string,item()*)) as xs:double
declare 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: pz
declare function pz($point as map(xs:string,item()*)) as xs:double
declare 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 { head(($point("z"), 0)) }
Function: pw
declare function pw($point as map(xs:string,item()*)) as xs:double
declare 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 { head(($point("w"), 0)) }
Function: coordinates
declare function coordinates($point as map(xs:string,item()*)) as xs:integer*
declare 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: coordinates
declare function coordinates($point as map(xs:string,item()*), $d as xs:integer) as xs:integer*
declare 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: pcoordinates
declare function pcoordinates($point as map(xs:string,item()*)) as xs:double*
declare 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: pcoordinates
declare function pcoordinates($point as map(xs:string,item()*), $d as xs:integer) as xs:double*
declare 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: pcoordinate
declare function pcoordinate($point as map(xs:string,item()*), $ix as xs:integer) as xs:double
declare 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: kind
declare function kind($point as map(xs:string,item()*)) as xs:string
declare 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-map
declare function property-map($region as map(xs:string,item()*)) as map(xs:string, item()*)
declare 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: properties
declare function properties($region as map(xs:string,item()*)) as xs:string*
declare 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-properties
declare function with-properties($region as map(xs:string,item()*),
$properties as map(xs:string, item()*)) as map(xs:string, item()*)
declare 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)) default return errors:error("GEOM-BADREGION", ($region, "with-properties")) }
Function: midpoint
declare function midpoint($a as map(xs:string,item()*), $b as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: magnitude
declare function magnitude($v as map(xs:string,item()*)) as xs:double
declare 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: dot
declare function dot($p1 as map(xs:string,item()*),
$p2 as map(xs:string,item()*)) as xs:double
declare 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: dot2
declare function dot2($p as map(xs:string,item()*)) as xs:double
declare 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: determinant
declare function determinant($u as map(xs:string,item()*),
$v as map(xs:string,item()*)) as xs:double
declare 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: cross
declare function cross($u as map(xs:string,item()*),
$v as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: times
declare function times($u as map(xs:string,item()*),
$k as xs:double) as map(xs:string,item()*)
declare 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: add
declare function add($u as map(xs:string,item()*),
$v as map(xs:string,item()*)) as map(xs:string,item()*)
declare function add($u as map(xs:string,item()*), $v as map(xs:string,item()*)) as map(xs:string,item()*)
add()
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: sum
declare function sum($pts as map(xs:string,item()*)*) as map(xs:string,item()*)
declare 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: sub
declare function sub($u as map(xs:string,item()*),
$v as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: minus
declare function minus($v as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: normalize
declare function normalize($v as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: perpendicular
declare function perpendicular($v as map(xs:string, item()*)) as map(xs:string, item()*)
declare 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: perpendiculars
declare function perpendiculars($v as map(xs:string,item()*)) as map(xs:string,item()*)*
declare 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-z
declare function perpendiculars-with-z($v as map(xs:string,item()*)) as map(xs:string,item()*)*
declare 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: snap
declare function snap($points as map(xs:string,item()*)*) as map(xs:string,item()*)*
declare 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: floor
declare function floor($points as map(xs:string,item()*)*) as map(xs:string,item()*)*
declare 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: decimal
declare function decimal($points as map(xs:string,item()*)*,
$digits as xs:integer) as map(xs:string,item()*)*
declare 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: quote
declare function quote($points as map(xs:string,item()*)*) as xs:string
declare 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: valid
declare function valid($point as map(xs:string,item()*)) as xs:boolean
declare 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: same
declare function same($this as map(xs:string,item()*),
$other as map(xs:string,item()*)) as xs:boolean
declare 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: same
declare function same($this as map(xs:string,item()*),
$other as map(xs:string,item()*),
$tolerance as xs:double) as xs:boolean
declare 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: mutate
declare 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()*)*
declare 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: distinct
declare function distinct($points as map(xs:string,item()*)*) as map(xs:string,item()*)*
declare 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 ( head($sorted), for $pt at $i in tail($sorted) where not(this:same($pt, $sorted[$i])) return $pt ) }
Function: unquote
declare function unquote($string as xs:string) as map(xs:string,item()*)
declare 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: map
declare function map($f as function(xs:double) as xs:double,
$point as map(xs:string,item()*)) as map(xs:string,item()*)
declare 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: map
declare 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()*)
declare 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: map2
declare 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()*)
declare 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: map2
declare 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()*)
declare 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: map3
declare 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()*)
declare 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: map3
declare 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()*)
declare 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: map4
declare 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()*)
declare 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: map4
declare 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()*)
declare 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: distance
declare function distance($p1 as map(xs:string,item()*),
$p2 as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function polar-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function taxi-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function avg-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function rail-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function rail-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*),
$origin as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function chebyshev-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function minkowski-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*),
$order as xs:double) as xs:double
declare 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-similarity
declare function cosine-similarity($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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-distance
declare function cosine-distance($p as map(xs:string,item()*),
$q as map(xs:string,item()*)) as xs:double
declare 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: angle
declare function angle($last as map(xs:string,item()*)?, $curr as map(xs:string,item()*)) as xs:double
declare 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)[1],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: inclination
declare function inclination($last as map(xs:string,item()*)?, $curr as map(xs:string,item()*)) as xs:double
declare 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)[1])=>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: destination
declare function destination($point as map(xs:string,item()*),
$degrees as xs:double,
$length as xs:double) as map(xs:string,item()*)
declare 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()*) { let $angle := util:radians($degrees) return this:point( this:px($point) + math:cos($angle)*$length, this:py($point) + math:sin($angle)*$length ) }
Function: destination
declare 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()*)
declare 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()*) { let $azimuth := util:radians($azimuth_degrees) let $inclination := util:radians($inclination_degrees) (: 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: sorientation
declare function sorientation($p as map(xs:string,item()*),
$q as map(xs:string,item()*),
$r as map(xs:string,item()*)) as xs:double
declare 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: orientation
declare function orientation($p as map(xs:string,item()*),
$q as map(xs:string,item()*),
$r as map(xs:string,item()*)) as xs:string
declare 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: centroid
declare function centroid($points as map(xs:string,item()*)*) as map(xs:string,item()*)
declare 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: counterclockwise
declare function counterclockwise($points as map(xs:string,item()*)*) as map(xs:string,item()*)*
declare 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: draw
declare function draw($item as map(xs:string,item()*),
$properties as map(xs:string,item()*),
$drawing as map(xs:string,function(*)?)) as item()*
declare 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). : : Copyright© Mary Holstege 2020-2023 : CC-BY (https://creativecommons.org/licenses/by/4.0/) : @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[1], $coordinates[2]) case 3 return this:point($coordinates[1], $coordinates[2], $coordinates[3]) case 4 return this:point($coordinates[1], $coordinates[2], $coordinates[3], $coordinates[4]) 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 :) )[1] }; (:~ : 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 { let $z := head(($point("z"),0)) 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 { let $w := head(($point("w"),0)) 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 { head(($point("z"), 0)) }; (:~ : 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 { head(($point("w"), 0)) }; (:~ : 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)) default return errors:error("GEOM-BADREGION", ($region, "with-properties")) }; (:======================================================================: : 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() : Add two points, returning the new point. : : @param $u: one point : @param $v: the other point :) 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) }; (:~ : 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 ( head($sorted), 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)[1],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)[1])=>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()*) { let $angle := util:radians($degrees) 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()*) { let $azimuth := util:radians($azimuth_degrees) let $inclination := util:radians($inclination_degrees) (: 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) };