# http://mathling.com/art/meanders  library module

http://mathling.com/art/meanders

Meanders: smooth meanders
Regular meander:
θ(s) = Σ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
θ = tangential angle, s=arc length
For set of A,λ,φ; let's take φ[i]=0
(x,y) = (∫[0:s] cos(θ(s)), ∫[0:s] sin(θ(s))

Looping meander()
dθ(s) = sΣ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
For set of A,λ,φ; let's take φ[i]=0
(x,y) = (∫[0:s] cos(dθ(s)), ∫[0:s] sin(dθ(s))
dθ(s) = dΣ[i=1:N](A[i] sin( (2π/λ[i])s ))
= Σ[i=1:N] d((A[i] sin( (2π/λ[i])s )))
= Σ[i=1:N] A[i] d((sin( (2π/λ[i])s )))
= Σ[i=1:N] A[i] (2π/λ[i]) cos( (2π/λ[i])s )

Example: Plot a random smooth meander in upper left of canvas

let \$box := box:box(0, 0, box:width(\$canvas) div 2, box:height(\$canvas) div 2): let \$path := meanders:meander(\$box, \$randomizers, \$parameters)
return draw:draw(\$path, map {"stroke":"blue","fill":"none","width":5})

Parameters:

Randomizers:
meanders.A.n: terms in the meander function; higher=more complex
meanders.A.initial: A1
meanders.A.ratio: A2/A1
meanders.λ.initial: λ1
meanders.λ.ratio: λ2/λ1
meanders.extent: extent of curve to draw
meanders.rotation: rotation to apply to curve

Rendering parameters:

mmm 2023
Status: Bleeding edge

### Imports

http://mathling.com/art/components
import module namespace components="http://mathling.com/art/components"
at "../art/components.xqy"
http://mathling.com/type/distribution
import module namespace dist="http://mathling.com/type/distribution"
at "../types/distributions.xqy"
http://mathling.com/type/wrapper
import module namespace wrapper="http://mathling.com/type/wrapper"
at "../types/wrapper.xqy"
http://mathling.com/geometric/rectangle
import module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy"
http://mathling.com/geometric/spline
import module namespace spline="http://mathling.com/geometric/spline"
at "../geo/spline.xqy"
http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric"
at "../geo/euclidean.xqy"
http://mathling.com/geometric/point
import module namespace point="http://mathling.com/geometric/point"
at "../geo/point.xqy"
http://mathling.com/core/random
import module namespace rand="http://mathling.com/core/random"
at "../core/random.xqy"
http://mathling.com/art/core
import module namespace core="http://mathling.com/art/core"
at "../art/core.xqy"
http://mathling.com/geometric/path
import module namespace path="http://mathling.com/geometric/path"
at "../geo/path.xqy"
http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy"
http://mathling.com/shape/paths
import module namespace paths="http://mathling.com/shape/paths"
at "../shapes/paths.xqy"
http://mathling.com/shape
import module namespace shapes="http://mathling.com/shape"
at "../shapes/shapes.xqy"
http://mathling.com/core/errors
import module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy"

### Functions

#### Function: componentsdeclare function components() as map(xs:string, map(xs:string,item()*))

Standard callback: Subcomponent map.
Example. render=true() and mode="default" are the defaults if unspecified
map {
"example":
map {
"namespace": "http://mathling.com/art/example",
"render": true(),
"mode": "default"
}
}

##### Returns
• map(xs:string,map(xs:string,item()*))
declare function this:components() as map(xs:string, map(xs:string,item()*))
{
components:expand(
map {
},
this:lookup#2
)
}

#### Function: rendering-parametersdeclare function rendering-parameters(\$canvas as map(xs:string,item()*), \$algorithm-parameters as map(xs:string,item()*)) as map(xs:string,item()*)

Standard callback: Set of rendering parameters.

##### Params
• canvas as map(xs:string,item()*): drawing canvas
• algorithm-parameters as map(xs:string,item()*): set of algorithm parameters
##### Returns
• map(xs:string,item()*)
declare function this:rendering-parameters(
\$canvas as map(xs:string,item()*),
\$algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
}
}

#### Function: algorithm-mode-parametersdeclare function algorithm-mode-parameters(\$mode as xs:string, \$resolution as xs:string, \$canvas as map(xs:string,item()*)) as map(xs:string,item()*)

Standard callback: Set of algorithm parameters specific to a particular mode.

##### Params
• mode as xs:string: selected mode
• resolution as xs:string: defined resolution
• canvas as map(xs:string,item()*): drawing canvas
##### Returns
• map(xs:string,item()*)
declare function this:algorithm-mode-parameters(
\$mode as xs:string,
\$resolution as xs:string,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
switch (\$mode)
case "default" return map {}
}

#### Function: algorithm-parametersdeclare function algorithm-parameters(\$resolution as xs:string, \$canvas as map(xs:string, item()*)) as map(xs:string,item()*)

Standard callback: Set of default algorithm parameters.

##### Params
• resolution as xs:string: defined resolution
• canvas as map(xs:string,item()*): drawing canvas
##### Returns
• map(xs:string,item()*)
declare function this:algorithm-parameters(
\$resolution as xs:string,
\$canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
map {
}
}

#### Function: randomizersdeclare function randomizers(\$canvas as map(xs:string,item()*), \$parameters as map(xs:string,item()*)) as map(xs:string,item()*)

Standard callback: Set of randomizers.

##### Params
• canvas as map(xs:string,item()*): drawing canvas
• parameters as map(xs:string,item()*): algorithm parameter bundle
##### Returns
• map(xs:string,item()*)
declare function this:randomizers(
\$canvas as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
"meanders.A.n": dist:binomial(4, 30),
"meanders.A.initial": dist:uniform(1.5, 2.2)=>dist:cast("decimal"),
"meanders.A.ratio": dist:uniform(0.15, 0.25)=>dist:cast("decimal"),
"meanders.λ.initial": dist:constant(1.0)=>dist:cast("decimal"),
"meanders.λ.ratio": dist:uniform(0.15, 0.55)=>dist:cast("decimal"),
"meanders.extent": dist:normal(2.0, 1.0)=>dist:min(0.25)=>dist:cast("decimal"),
"meanders.rotation": \$rand:STD-DEGREES=>dist:cast("decimal")
}
}

#### Function: colophondeclare function colophon(\$parameters as map(xs:string,item()*)) as xs:string?

Standard callback: component colophon (string attached to signature).

##### Params
• parameters as map(xs:string,item()*): algorithm parameter bundle
##### Returns
• xs:string?
declare function this:colophon(\$parameters as map(xs:string,item()*)) as xs:string?
{
"Smooth meanders: θ(s) = Σ[i=1:N](A[i] sin(2π/λ[i])s);
Looping meanders: dθ(s) = sΣ[i=1:N](A[i] sin(2π/λ[i])s);
θ=tangential angle; plotted via Whewell equation
"
}

and parameters)

##### Params
• canvas as map(xs:string,item()*): drawing canvas
• randomizers as map(xs:string,item()*): active randomizers for component
• parameters as map(xs:string,item()*): active parameters for component
\$canvas as map(xs:string,item()*),
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
)
{
()
}

#### Function: meanderdeclare function meander(\$canvas as map(xs:string, item()*), \$randomizers as map(xs:string, item()*), \$parameters as map(xs:string, item()*))

meander()
Construct a random smooth meander that fits the canvas.

\$canvas: space for meander; it will be centered and scaled to this space
\$randomizers: randomizers to use
\$parameters: parameters to use

##### Params
• canvas as map(xs:string,item()*)
• randomizers as map(xs:string,item()*)
• parameters as map(xs:string,item()*)
declare function this:meander(
\$canvas as map(xs:string, item()*),
\$randomizers as map(xs:string, item()*),
\$parameters as map(xs:string, item()*)
)
{
let \$dynamics :=
core:random-parameters(
core:random-parameter-map((
"meanders.A.n",
"meanders.extent", "meanders.rotation"
)),
\$randomizers
)
let \$n := core:parameter("meanders.A.n", \$dynamics)
let \$A-initial := core:parameter("meanders.A.initial", \$dynamics)
let \$A-ratio := core:parameter("meanders.A.ratio", \$dynamics)
let \$λ-initial := core:parameter("meanders.λ.initial", \$dynamics)
let \$λ-ratio := core:parameter("meanders.λ.ratio", \$dynamics)
let \$As := (\$A-initial, reverse(tail(util:powspace(\$n, \$A-fade)))!util:decimal(. * \$A-ratio, 2))
let \$λs := (\$λ-initial, reverse(tail(util:powspace(\$n, \$λ-fade)))!util:decimal(. * \$λ-ratio, 2))
let \$extent := core:parameter("meanders.extent", \$dynamics)
let \$rotation := core:parameter("meanders.rotation", \$dynamics)
let \$center := box:center(\$canvas)
let \$radius := min((box:width(\$canvas), box:height(\$canvas))) div 2
let \$n-points := util:round(box:diagonal(\$canvas) * 2)
let \$stroke := (
paths:meander(
\$As, \$λs,
\$n-points, \$extent, false()
)=>geom:decimal(1)=>spline:spline()=>
geom:mutate(function(\$p) {\$p=>point:as-dimension(2)})=>
geom:rotate(\$rotation, \$center)=>
shapes:scale-and-center(\$canvas)=>
path:with-edge-ts()
)
return (
wrapper:wrapper(
\$stroke,
\$dynamics
)
)
}

#### Function: looping-meanderdeclare function looping-meander(\$canvas as map(xs:string, item()*), \$randomizers as map(xs:string, item()*), \$parameters as map(xs:string, item()*))

looping-meander()
Construct a random smooth looping meander that fits the canvas.

\$canvas: space for meander; it will be centered and scaled to this space
\$randomizers: randomizers to use
\$parameters: parameters to use

##### Params
• canvas as map(xs:string,item()*)
• randomizers as map(xs:string,item()*)
• parameters as map(xs:string,item()*)
declare function this:looping-meander(
\$canvas as map(xs:string, item()*),
\$randomizers as map(xs:string, item()*),
\$parameters as map(xs:string, item()*)
)
{
let \$dynamics :=
core:random-parameters(
core:random-parameter-map((
"meanders.A.n",
"meanders.extent", "meanders.rotation"
)),
\$randomizers
)
let \$n := core:parameter("meanders.A.n", \$dynamics)
let \$A-initial := core:parameter("meanders.A.initial", \$dynamics)
let \$A-ratio := core:parameter("meanders.A.ratio", \$dynamics)
let \$λ-ratio := core:parameter("meanders.λ.ratio", \$dynamics)
let \$λ-initial := core:parameter("meanders.λ.initial", \$dynamics)
let \$As := (\$A-initial, reverse(tail(util:powspace(\$n, \$A-fade)))!util:decimal(. * \$A-ratio, 2))
let \$λs := (\$λ-initial, reverse(tail(util:powspace(\$n, \$λ-fade)))!util:decimal(. * \$λ-ratio, 2))
let \$extent := core:parameter("meanders.extent", \$dynamics)
let \$rotation := core:parameter("meanders.rotation", \$dynamics)
let \$center := box:center(\$canvas)
let \$radius := min((box:width(\$canvas), box:height(\$canvas))) div 2
let \$n-points := util:round(box:diagonal(\$canvas) * 2)
let \$stroke := (
paths:looping-meander(
\$As, \$λs,
\$n-points, \$extent, false()
)=>geom:decimal(1)=>spline:spline()=>
geom:mutate(function(\$p) {\$p=>point:as-dimension(2)})=>
geom:rotate(\$rotation, \$center)=>
shapes:scale-and-center(\$canvas)=>
path:with-edge-ts()
)
return (
wrapper:wrapper(
\$stroke,
\$dynamics
)
)
}

### Original Source Code

xquery version "3.1";
(:~
: Meanders: smooth meanders
: Regular meander:
: θ(s) = Σ[i=1:N](A[i] sin( (2π/λ[i])s + φ[i] )
: θ = tangential angle, s=arc length
: For set of A,λ,φ; let's take φ[i]=0
: (x,y) = (∫[0:s] cos(θ(s)), ∫[0:s] sin(θ(s))
:
: Looping meander()
: dθ(s) = sΣ[i=1:N](A[i] sin( (2π/λ[i])s  + φ[i] )
: For set of A,λ,φ; let's take φ[i]=0
: (x,y) = (∫[0:s] cos(dθ(s)), ∫[0:s] sin(dθ(s))
: dθ(s) = dΣ[i=1:N](A[i] sin( (2π/λ[i])s ))
:       = Σ[i=1:N] d((A[i] sin( (2π/λ[i])s )))
:       = Σ[i=1:N] A[i] d((sin( (2π/λ[i])s )))
:       = Σ[i=1:N] A[i] (2π/λ[i]) cos( (2π/λ[i])s )
:
: Example: Plot a random smooth meander in upper left of canvas
:
: let \$box := box:box(0, 0, box:width(\$canvas) div 2, box:height(\$canvas) div 2): let \$path := meanders:meander(\$box, \$randomizers, \$parameters)
: return draw:draw(\$path, map {"stroke":"blue","fill":"none","width":5})
:
: Parameters:
:
: Randomizers:
: meanders.A.n: terms in the meander function; higher=more complex
: meanders.A.initial: A1
: meanders.A.ratio: A2/A1
: meanders.A.fade: decay rate for A3...An
: meanders.λ.initial: λ1
: meanders.λ.ratio: λ2/λ1
: meanders.λ.fade: decay rate for λ3...λn
: meanders.extent: extent of curve to draw
: meanders.rotation: rotation to apply to curve
:
: Rendering parameters:
:
: @since mmm 2023
: @custom:Status Bleeding edge
:)
module namespace this="http://mathling.com/art/meanders";

import module namespace core="http://mathling.com/art/core"
at "../art/core.xqy";
import module namespace components="http://mathling.com/art/components"
at "../art/components.xqy";
import module namespace errors="http://mathling.com/core/errors"
at "../core/errors.xqy";
import module namespace rand="http://mathling.com/core/random"
at "../core/random.xqy";
import module namespace dist="http://mathling.com/type/distribution"
at "../types/distributions.xqy";
import module namespace wrapper="http://mathling.com/type/wrapper"
at "../types/wrapper.xqy";
import module namespace util="http://mathling.com/core/utilities"
at "../core/utilities.xqy";
import module namespace geom="http://mathling.com/geometric"
at "../geo/euclidean.xqy";
import module namespace point="http://mathling.com/geometric/point"
at "../geo/point.xqy";
import module namespace box="http://mathling.com/geometric/rectangle"
at "../geo/rectangle.xqy";
import module namespace spline="http://mathling.com/geometric/spline"
at "../geo/spline.xqy";
import module namespace path="http://mathling.com/geometric/path"
at "../geo/path.xqy";
import module namespace paths="http://mathling.com/shape/paths"
at "../shapes/paths.xqy";
import module namespace shapes="http://mathling.com/shape"
at "../shapes/shapes.xqy";

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

(:~
: In XQuery this is pointless. It is here for single-sourcing, because in
: XSLT we have to make sure the lookup happens in the right context. So:
: a bit of a hack, to be sure.
:)
declare %private function this:lookup(\$qname as xs:QName, \$n as xs:integer) as function(*)?
{
function-lookup(\$qname, \$n)
};

(:~
: Standard callback: Subcomponent map.
: Example. render=true() and mode="default" are the defaults if unspecified
: map {
:   "example":
:     map {
:       "namespace": "http://mathling.com/art/example",
:       "render": true(),
:       "mode": "default"
:     }
: }
:)
declare function this:components() as map(xs:string, map(xs:string,item()*))
{
components:expand(
map {
},
this:lookup#2
)
};

(:~
: Standard callback: Set of rendering parameters.
: @param \$canvas: drawing canvas
: @param \$algorithm-parameters: set of algorithm parameters
:)
declare function this:rendering-parameters(
\$canvas as map(xs:string,item()*),
\$algorithm-parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
}
};

(:~
: Standard callback: Set of algorithm parameters specific to a particular mode.
: @param \$mode: selected mode
: @param \$resolution: defined resolution
: @param \$canvas: drawing canvas
:)
declare function this:algorithm-mode-parameters(
\$mode as xs:string,
\$resolution as xs:string,
\$canvas as map(xs:string,item()*)
) as map(xs:string,item()*)
{
switch (\$mode)
case "default" return map {}
};

(:~
: Standard callback: Set of default algorithm parameters.
: @param \$resolution: defined resolution
: @param \$canvas: drawing canvas
:)
declare function this:algorithm-parameters(
\$resolution as xs:string,
\$canvas as map(xs:string, item()*)
) as map(xs:string,item()*)
{
map {
}
};

(:~
: Standard callback: Set of randomizers.
: @param \$canvas: drawing canvas
: @param \$parameters: algorithm parameter bundle
:)
declare function this:randomizers(
\$canvas as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
) as map(xs:string,item()*)
{
map {
"meanders.A.n": dist:binomial(4, 30),
"meanders.A.initial": dist:uniform(1.5, 2.2)=>dist:cast("decimal"),
"meanders.A.ratio": dist:uniform(0.15, 0.25)=>dist:cast("decimal"),
"meanders.λ.initial": dist:constant(1.0)=>dist:cast("decimal"),
"meanders.λ.ratio": dist:uniform(0.15, 0.55)=>dist:cast("decimal"),
"meanders.extent": dist:normal(2.0, 1.0)=>dist:min(0.25)=>dist:cast("decimal"),
"meanders.rotation": \$rand:STD-DEGREES=>dist:cast("decimal")
}
};

(:~
: Standard callback: component colophon (string attached to signature).
: @param \$parameters: algorithm parameter bundle
:)
declare function this:colophon(\$parameters as map(xs:string,item()*)) as xs:string?
{
"Smooth meanders: θ(s) = Σ[i=1:N](A[i] sin(2π/λ[i])s);
Looping meanders: dθ(s) = sΣ[i=1:N](A[i] sin(2π/λ[i])s);
θ=tangential angle; plotted via Whewell equation
"
};

(:~
: and parameters)
:
: @param \$canvas: drawing canvas
: @param \$randomizers: active randomizers for component
: @param \$parameters: active parameters for component
:)
\$canvas as map(xs:string,item()*),
\$randomizers as map(xs:string,item()*),
\$parameters as map(xs:string,item()*)
)
{
()
};

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

(:~
: meander()
: Construct a random smooth meander that fits the canvas.
:
: \$canvas: space for meander; it will be centered and scaled to this space
: \$randomizers: randomizers to use
: \$parameters: parameters to use
: @return Scaled spline wrapped with dynamic parameters
:)
declare function this:meander(
\$canvas as map(xs:string, item()*),
\$randomizers as map(xs:string, item()*),
\$parameters as map(xs:string, item()*)
)
{
let \$dynamics :=
core:random-parameters(
core:random-parameter-map((
"meanders.A.n",
"meanders.extent", "meanders.rotation"
)),
\$randomizers
)
let \$n := core:parameter("meanders.A.n", \$dynamics)
let \$A-initial := core:parameter("meanders.A.initial", \$dynamics)
let \$A-ratio := core:parameter("meanders.A.ratio", \$dynamics)
let \$λ-initial := core:parameter("meanders.λ.initial", \$dynamics)
let \$λ-ratio := core:parameter("meanders.λ.ratio", \$dynamics)
let \$As := (\$A-initial, reverse(tail(util:powspace(\$n, \$A-fade)))!util:decimal(. * \$A-ratio, 2))
let \$λs := (\$λ-initial, reverse(tail(util:powspace(\$n, \$λ-fade)))!util:decimal(. * \$λ-ratio, 2))
let \$extent := core:parameter("meanders.extent", \$dynamics)
let \$rotation := core:parameter("meanders.rotation", \$dynamics)
let \$center := box:center(\$canvas)
let \$radius := min((box:width(\$canvas), box:height(\$canvas))) div 2
let \$n-points := util:round(box:diagonal(\$canvas) * 2)
let \$stroke := (
paths:meander(
\$As, \$λs,
\$n-points, \$extent, false()
)=>geom:decimal(1)=>spline:spline()=>
geom:mutate(function(\$p) {\$p=>point:as-dimension(2)})=>
geom:rotate(\$rotation, \$center)=>
shapes:scale-and-center(\$canvas)=>
path:with-edge-ts()
)
return (
wrapper:wrapper(
\$stroke,
\$dynamics
)
)
};

(:~
: looping-meander()
: Construct a random smooth looping meander that fits the canvas.
:
: \$canvas: space for meander; it will be centered and scaled to this space
: \$randomizers: randomizers to use
: \$parameters: parameters to use
: @return Scaled spline wrapped with dynamic parameters
:)
declare function this:looping-meander(
\$canvas as map(xs:string, item()*),
\$randomizers as map(xs:string, item()*),
\$parameters as map(xs:string, item()*)
)
{
let \$dynamics :=
core:random-parameters(
core:random-parameter-map((
"meanders.A.n",
"meanders.extent", "meanders.rotation"
)),
\$randomizers
)
let \$n := core:parameter("meanders.A.n", \$dynamics)
let \$A-initial := core:parameter("meanders.A.initial", \$dynamics)
let \$A-ratio := core:parameter("meanders.A.ratio", \$dynamics)
let \$λ-ratio := core:parameter("meanders.λ.ratio", \$dynamics)
let \$λ-initial := core:parameter("meanders.λ.initial", \$dynamics)
let \$As := (\$A-initial, reverse(tail(util:powspace(\$n, \$A-fade)))!util:decimal(. * \$A-ratio, 2))
let \$λs := (\$λ-initial, reverse(tail(util:powspace(\$n, \$λ-fade)))!util:decimal(. * \$λ-ratio, 2))
let \$extent := core:parameter("meanders.extent", \$dynamics)
let \$rotation := core:parameter("meanders.rotation", \$dynamics)
let \$center := box:center(\$canvas)
let \$radius := min((box:width(\$canvas), box:height(\$canvas))) div 2
let \$n-points := util:round(box:diagonal(\$canvas) * 2)
let \$stroke := (
paths:looping-meander(
\$As, \$λs,
\$n-points, \$extent, false()
)=>geom:decimal(1)=>spline:spline()=>
geom:mutate(function(\$p) {\$p=>point:as-dimension(2)})=>
geom:rotate(\$rotation, \$center)=>
shapes:scale-and-center(\$canvas)=>
path:with-edge-ts()
)
return (
wrapper:wrapper(
\$stroke,
\$dynamics
)
)
};