http://mathling.com/image/matrix  library module

http://mathling.com/image/matrix


2D array of colours similar functionality to point matrix, but with
sequences of colours rather than a map. Quicker for creation if you
don't need the sparseness.

Copyright© Mary Holstege 2021-2023
CC-BY (https://creativecommons.org/licenses/by/4.0/)

December 2021
Status: Stable

Imports

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

Variables

Variable: $RESERVED as 

Functions

Function: array
declare function array($rows as xs:integer, $columns as xs:integer, $data as map(xs:string,item()*)*) as map(*)

Params
  • rows as xs:integer
  • columns as xs:integer
  • data as map(xs:string,item()*)*
Returns
  • map(*)
declare function this:array(
  $rows as xs:integer,
  $columns as xs:integer,
  $data as map(xs:string,item()*)*
) as map(*)
{
  if ($rows * $columns ne count($data))
  then errors:error("UTIL-BADARRAY", ($rows, $columns, count($data)))
  else (
    map {
      "kind": "imagearray",
      "rows": $rows,
      "columns": $columns,
      "data": $data
    }
  )
}

Function: kind
declare function kind($array as map(*)) as xs:string

Params
  • array as map(*)
Returns
  • xs:string
declare function this:kind($array as map(*)) as xs:string
{
  $array("kind")
}

Function: rows
declare function rows($array as map(*)) as xs:integer

Params
  • array as map(*)
Returns
  • xs:integer
declare function this:rows($array as map(*)) as xs:integer
{
  $array("rows")
}

Function: columns
declare function columns($array as map(*)) as xs:integer

Params
  • array as map(*)
Returns
  • xs:integer
declare function this:columns($array as map(*)) as xs:integer
{
  $array("columns")
}

Function: data
declare function data($array as map(*)) as map(xs:string,item()*)*

Params
  • array as map(*)
Returns
  • map(xs:string,item()*)*
declare function this:data($array as map(*)) as map(xs:string,item()*)*
{
  $array("data")
}

Function: get
declare function get($matrix as map(*), $row as xs:integer, $column as xs:integer) as map(xs:string,item()*)

Params
  • matrix as map(*)
  • row as xs:integer
  • column as xs:integer
Returns
  • map(xs:string,item()*)
declare function this:get(
  $matrix as map(*),
  $row as xs:integer,
  $column as xs:integer
) as map(xs:string,item()*)
{
  this:data($matrix)[($row - 1)*this:columns($matrix) + $column]
}

Function: get
declare function get($matrix as map(*), $point as map(xs:string,item()*)) as map(xs:string,item()*)

Params
  • matrix as map(*)
  • point as map(xs:string,item()*)
Returns
  • map(xs:string,item()*)
declare function this:get(
  $matrix as map(*),
  $point as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  this:data($matrix)[(point:y($point) - 1)*this:columns($matrix) + point:x($point)]
}

Function: row
declare function row($matrix as map(*), $row as xs:integer) as map(xs:string,item()*)*


row()
Return all the values from the given row

Params
  • matrix as map(*): the matrix
  • row as xs:integer: the row number
Returns
  • map(xs:string,item()*)*
declare function this:row(
  $matrix as map(*),
  $row as xs:integer
) as map(xs:string,item()*)*
{
  if ($row < 1 or $row gt $matrix("rows")) then () else
  let $data := $matrix("data")
  let $num-columns := $matrix("columns")
  let $ixs :=
    for $col in 1 to $num-columns
    return ($row - 1)*$num-columns + $col
  (: Following works because indexes unique and in order :)
  return $data[position()=$ixs]
}

Function: column
declare function column($matrix as map(*), $column as xs:integer) as map(xs:string,item()*)*


column()
Return all the values from the given column

Params
  • matrix as map(*): the matrix
  • column as xs:integer: the column number
Returns
  • map(xs:string,item()*)*
declare function this:column(
  $matrix as map(*),
  $column as xs:integer
) as map(xs:string,item()*)*
{
  if ($column lt 1 or $column gt $matrix("columns")) then () else
  let $data := $matrix("data")
  let $num-rows := $matrix("rows")
  let $num-columns := $matrix("columns")
  let $ixs :=
    for $row in 1 to $num-rows
    return ($row - 1)*$num-columns + $column
  (: Following works because indexes unique and in order :)
  return $data[position()=$ixs]
}

Function: diagonal
declare function diagonal($matrix as map(*)) as map(xs:string,item()*)*


diagonal()
Return all the values from the diagonal
Raises an error if the matrix is not square

Params
  • matrix as map(*): the matrix
Returns
  • map(xs:string,item()*)*
declare function this:diagonal($matrix as map(*)) as map(xs:string,item()*)*
{
  let $data := $matrix("data")
  let $num-rows := $matrix("rows")
  let $num-columns := $matrix("columns")
  return (
    if ($num-rows != $num-columns) then (
      errors:error("UTIL-MATRIXNOTSQUARE", ($num-rows,$num-columns))
    ) else (
      let $ixs := for $i in 1 to $num-rows return ($i - 1)*$num-columns + $i
      (: Following works because indexes unique and in order :)
      return $data[position()=$ixs]
    )
  )
}

Function: sample
declare function sample($matrix as map(*), $row as xs:integer, $column as xs:integer, $sample-size as xs:integer) as map(xs:string,item()*)*


sample()
Return all the values in a sample of the matrix from row,column and
extending for sample-size in each direction (if possible). Pad missing
data with data from last row or column, repeated.

Params
  • matrix as map(*): the matrix
  • row as xs:integer: starting row
  • column as xs:integer: starting column
  • sample-size as xs:integer: extent of sample
Returns
  • map(xs:string,item()*)*
declare function this:sample(
  $matrix as map(*),
  $row as xs:integer,
  $column as xs:integer,
  $sample-size as xs:integer
) as map(xs:string,item()*)*
{
  if ($row lt 1 or $row gt $matrix("rows")) then (util:log("bad row "||$row)) else
  if ($column lt 1 or $column gt $matrix("columns")) then (util:log("bad column "||$column)) else
  let $data := $matrix("data")
  let $num-rows := $matrix("rows")
  let $num-columns := $matrix("columns")
  return (
    (: If we don't have to pad, we can use a faster approach :)
    if ($row + $sample-size - 1 <= $num-rows and
        $column + $sample-size - 1 <= $num-columns)
    then (
      let $ixs := (
        for $r in $row to $row + $sample-size - 1
        for $c in $column to $column + $sample-size - 1
        return (
          ($r - 1)*$num-columns + $c
        )
      )
      return (
        (: Following works because indexes unique and in order :)
        $data[position()=$ixs]
      )
    ) else (
      let $ixs := (
        for $r in $row to $row + $sample-size - 1
        for $c in $column to $column + $sample-size - 1
        let $r := if ($r > $num-rows) then $num-rows else $r
        let $c := if ($c > $num-columns) then $num-columns else $c
        return (
          ($r - 1)*$num-columns + $c
        )
      )
      return (
        (: We may have duplicate/out of order indices: have to iterate :)
        for-each($ixs, function($ix as xs:integer) as map(xs:string,item()*)? { $data[position()=$ix] })
      )
    )
  )
}

Function: data
declare function data($array as map(*), $data as map(xs:string,item()*)*) as map(*)


data()
Replace the data.

Params
  • array as map(*)
  • data as map(xs:string,item()*)*
Returns
  • map(*)
declare function this:data($array as map(*), $data as map(xs:string,item()*)*) as map(*)
{
  if (this:rows($array) * this:columns($array) ne count($data))
  then errors:error("UTIL-BADARRAY", (this:rows($array), this:columns($array), count($data)))
  else (
    $array=>map:put("data", $data)
  )
}

Original Source Code

xquery version "3.1";
(:~
 : 2D array of colours similar functionality to point matrix, but with
 : sequences of colours rather than a map. Quicker for creation if you
 : don't need the sparseness.
 :
 : Copyright© Mary Holstege 2021-2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since December 2021
 : @custom:Status Stable
 :)
module namespace this="http://mathling.com/image/matrix"; 

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

declare namespace map="http://www.w3.org/2005/xpath-functions/map";
declare namespace math="http://www.w3.org/2005/xpath-functions/math";

declare variable $this:RESERVED := ("kind","rows","columns","data");

declare function this:array(
  $rows as xs:integer,
  $columns as xs:integer,
  $data as map(xs:string,item()*)*
) as map(*)
{
  if ($rows * $columns ne count($data))
  then errors:error("UTIL-BADARRAY", ($rows, $columns, count($data)))
  else (
    map {
      "kind": "imagearray",
      "rows": $rows,
      "columns": $columns,
      "data": $data
    }
  )
};

declare function this:kind($array as map(*)) as xs:string
{
  $array("kind")
};

declare function this:rows($array as map(*)) as xs:integer
{
  $array("rows")
};

declare function this:columns($array as map(*)) as xs:integer
{
  $array("columns")
};

declare function this:data($array as map(*)) as map(xs:string,item()*)*
{
  $array("data")
};

declare function this:get(
  $matrix as map(*),
  $row as xs:integer,
  $column as xs:integer
) as map(xs:string,item()*)
{
  this:data($matrix)[($row - 1)*this:columns($matrix) + $column]
};

declare function this:get(
  $matrix as map(*),
  $point as map(xs:string,item()*)
) as map(xs:string,item()*)
{
  this:data($matrix)[(point:y($point) - 1)*this:columns($matrix) + point:x($point)]
};

(:~
 : row()
 : Return all the values from the given row
 : 
 : @param $matrix: the matrix
 : @param $row: the row number
 :)
declare function this:row(
  $matrix as map(*),
  $row as xs:integer
) as map(xs:string,item()*)*
{
  if ($row < 1 or $row gt $matrix("rows")) then () else
  let $data := $matrix("data")
  let $num-columns := $matrix("columns")
  let $ixs :=
    for $col in 1 to $num-columns
    return ($row - 1)*$num-columns + $col
  (: Following works because indexes unique and in order :)
  return $data[position()=$ixs]
};

(:~
 : column()
 : Return all the values from the given column
 : 
 : @param $matrix: the matrix
 : @param $column: the column number
 :)
declare function this:column(
  $matrix as map(*),
  $column as xs:integer
) as map(xs:string,item()*)*
{
  if ($column lt 1 or $column gt $matrix("columns")) then () else
  let $data := $matrix("data")
  let $num-rows := $matrix("rows")
  let $num-columns := $matrix("columns")
  let $ixs :=
    for $row in 1 to $num-rows
    return ($row - 1)*$num-columns + $column
  (: Following works because indexes unique and in order :)
  return $data[position()=$ixs]
};

(:~
 : diagonal()
 : Return all the values from the diagonal
 : Raises an error if the matrix is not square
 : 
 : @param $matrix: the matrix
 :)
declare function this:diagonal($matrix as map(*)) as map(xs:string,item()*)*
{
  let $data := $matrix("data")
  let $num-rows := $matrix("rows")
  let $num-columns := $matrix("columns")
  return (
    if ($num-rows != $num-columns) then (
      errors:error("UTIL-MATRIXNOTSQUARE", ($num-rows,$num-columns))
    ) else (
      let $ixs := for $i in 1 to $num-rows return ($i - 1)*$num-columns + $i
      (: Following works because indexes unique and in order :)
      return $data[position()=$ixs]
    )
  )
};

(:~
 : sample()
 : Return all the values in a sample of the matrix from row,column and
 : extending for sample-size in each direction (if possible). Pad missing
 : data with data from last row or column, repeated.
 : 
 : @param $matrix: the matrix
 : @param $row: starting row
 : @param $column: starting column
 : @param $sample-size: extent of sample
 :)
declare function this:sample(
  $matrix as map(*),
  $row as xs:integer,
  $column as xs:integer,
  $sample-size as xs:integer
) as map(xs:string,item()*)*
{
  if ($row lt 1 or $row gt $matrix("rows")) then (util:log("bad row "||$row)) else
  if ($column lt 1 or $column gt $matrix("columns")) then (util:log("bad column "||$column)) else
  let $data := $matrix("data")
  let $num-rows := $matrix("rows")
  let $num-columns := $matrix("columns")
  return (
    (: If we don't have to pad, we can use a faster approach :)
    if ($row + $sample-size - 1 <= $num-rows and
        $column + $sample-size - 1 <= $num-columns)
    then (
      let $ixs := (
        for $r in $row to $row + $sample-size - 1
        for $c in $column to $column + $sample-size - 1
        return (
          ($r - 1)*$num-columns + $c
        )
      )
      return (
        (: Following works because indexes unique and in order :)
        $data[position()=$ixs]
      )
    ) else (
      let $ixs := (
        for $r in $row to $row + $sample-size - 1
        for $c in $column to $column + $sample-size - 1
        let $r := if ($r > $num-rows) then $num-rows else $r
        let $c := if ($c > $num-columns) then $num-columns else $c
        return (
          ($r - 1)*$num-columns + $c
        )
      )
      return (
        (: We may have duplicate/out of order indices: have to iterate :)
        for-each($ixs, function($ix as xs:integer) as map(xs:string,item()*)? { $data[position()=$ix] })
      )
    )
  )
};

(:~
 : data()
 : Replace the data.
 :)
declare function this:data($array as map(*), $data as map(xs:string,item()*)*) as map(*)
{
  if (this:rows($array) * this:columns($array) ne count($data))
  then errors:error("UTIL-BADARRAY", (this:rows($array), this:columns($array), count($data)))
  else (
    $array=>map:put("data", $data)
  )
};