http://mathling.com/geometric/kml  library module

http://mathling.com/geometric/kml


KML: parse to geometric objects
Pretty basic:
Utterly oblivious to coordinate systems; you'll want to map to canvas space.
Treats altitude as z coordinate
Doesn't honour modifiers like extrude, altitudeMode, etc.


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

June 2023
Status: Bleeding edge

Imports

http://mathling.com/geometric/path
import module namespace path="http://mathling.com/geometric/path"
       at "../geo/path.xqy"
http://mathling.com/geometric/edge
import module namespace edge="http://mathling.com/geometric/edge"
       at "../geo/edge.xqy"
http://mathling.com/core/utilities
import module namespace util="http://mathling.com/core/utilities"
       at "../core/utilities.xqy"
http://mathling.com/geometric/complex-polygon
import module namespace cpoly="http://mathling.com/geometric/complex-polygon"
       at "../geo/complex-polygon.xqy"
http://mathling.com/geometric/box
import module namespace box="http://mathling.com/geometric/box"
       at "../geo/box.xqy"
http://mathling.com/geometric/ellipse
import module namespace ellipse="http://mathling.com/geometric/ellipse"
       at "../geo/ellipse.xqy"
http://mathling.com/type/slot
import module namespace slot="http://mathling.com/type/slot"
       at "../types/slot.xqy"
http://mathling.com/geometric
import module namespace geom="http://mathling.com/geometric"
       at "../geo/euclidean.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"

Functions

Function: parse
declare function parse($kml as element(kml:kml), $parsemeta as function(element()) as map(xs:string,item()*)) as map(xs:string,item()*)*


parse()
Parse KML, pulling out Geometry elements and turning them into geometric
objects. Everything within a Folder will be combined into one slot, as
will MultiGeometry contents.

Params
  • kml as element(kml:kml)
  • parsemeta as function(element())asmap(xs:string,item()*)
Returns
  • map(xs:string,item()*)*
declare function this:parse(
  $kml as element(kml:kml),
  $parsemeta as function(element()) as map(xs:string,item()*)  
) as map(xs:string,item()*)*
{
  for $feature in $kml/(kml:Placemark|kml:Folder|kml:Document)
  return this:parse-feature($feature, $parsemeta)
}

Function: parse
declare function parse($kml as element(kml:kml)) as map(xs:string,item()*)*

Params
  • kml as element(kml:kml)
Returns
  • map(xs:string,item()*)*
declare function this:parse(
  $kml as element(kml:kml)
) as map(xs:string,item()*)*
{
  this:parse($kml, this:simple-meta#1)
}

Function: parse-geometry
declare function parse-geometry($kml as element(), $parsemeta as function(element()) as map(xs:string,item()*)) as map(xs:string,item()*)*


parse-geometry()
Parse a Geometry KML element into geometric objects.

Params
  • kml as element()
  • parsemeta as function(element())asmap(xs:string,item()*)
Returns
  • map(xs:string,item()*)*
declare function this:parse-geometry(
  $kml as element(),
  $parsemeta as function(element()) as map(xs:string,item()*)  
) as map(xs:string,item()*)*
{
  (
    typeswitch($kml)
    case element(kml:Point) return (
      let $coordinates := tokenize(normalize-space(data($kml/kml:coordinates)),",")[. ne ""]
      return point:vector($coordinates!number(.))
    )
    case element(kml:LineString) return (
      let $points := tokenize(normalize-space(data($kml/kml:coordinates)),"[\p{Z}]+")[. ne ""]
      return (
        path:path(edge:to-edges(
          for $p in $points
          let $pcoords := tokenize($p,",")[. ne ""]
          return point:vector($pcoords!number(.))
        ))
      )
    )
    case element(kml:LinearRing) return (
      let $points := tokenize(normalize-space(data($kml/kml:coordinates)),"[\p{Z}]+")[. ne ""]
      return (
        path:polygon(edge:to-edges(
          for $p in $points
          let $pcoords := tokenize($p,",")[. ne ""]
          return point:vector($pcoords!number(.))
        ))
      )
    )
    case element(kml:Polygon) return (
      let $outer := this:parse-geometry($kml/kml:outerBoundaryIs/kml:LinearRing)
      let $inners := (
        for $inner in $kml/kml:innerBoundaryIs/kml:LinearRing
        return this:parse-geometry($inner)
      )
      return (
        cpoly:complex-polygon($outer, $inners)
      )
    )
    case element(kml:MultiGeometry) return (
      slot:slot(
        for $e in $kml/* return this:parse-geometry($e)
      )
    )
    (: case element(kml:Model) return () DAE/COLLADA file linked :)
    default return errors:error("GEOM-NOTIMPLEMENTED", ("parse-geometry", name($kml)))
  )=>geom:with-properties($parsemeta($kml))
}

Function: parse-geometry
declare function parse-geometry($kml as element()) as map(xs:string,item()*)

Params
  • kml as element()
Returns
  • map(xs:string,item()*)
declare function this:parse-geometry(
  $kml as element()
) as map(xs:string,item()*)
{
  this:parse-geometry($kml, this:simple-meta#1)
}

Function: parse-feature
declare function parse-feature($kml as element(), $parsemeta as function(element()) as map(xs:string,item()*)) as map(xs:string,item()*)


parse-feature()
Parse a KML Feature element, pulling out Geometry elements and turning them
into geometric objects.

Params
  • kml as element()
  • parsemeta as function(element())asmap(xs:string,item()*)
Returns
  • map(xs:string,item()*)
declare function this:parse-feature(
  $kml as element(),
  $parsemeta as function(element()) as map(xs:string,item()*)  
) as map(xs:string,item()*)
{
  typeswitch($kml)
  case element(kml:Placemark) return (
    slot:slot(
      for $e in $kml/(kml:Point|kml:LineString|kml:LinearRing|kml:Polygon|kml:MultiGeometry)
      return this:parse-geometry($e),
      $parsemeta($kml)
    )
  )
  case element(kml:Folder) return (
    slot:slot(
      for $feature in $kml/(kml:Placemark|kml:Folder|kml:Document)
      return this:parse-feature($feature, $parsemeta),
      $parsemeta($kml)
    )
  )
  case element(kml:Document) return (
    slot:slot(
      for $feature in $kml/(kml:Placemark|kml:Folder|kml:Document)
      return this:parse-feature($feature, $parsemeta),
      $parsemeta($kml)
    )
  )
  default return errors:error("GEOM-NOTIMPLEMENTED", ("parse-feature", name($kml)))
}

Function: parse-feature
declare function parse-feature($kml as element()) as map(xs:string,item()*)

Params
  • kml as element()
Returns
  • map(xs:string,item()*)
declare function this:parse-feature(
  $kml as element()
) as map(xs:string,item()*)
{
  this:parse-feature($kml, this:simple-meta#1)
}

Function: simple-meta
declare function simple-meta($kml as element()) as map(xs:string,item()*)


simple-meta()
Parsing function for metadata that just grabs the description as a string
as well as the id and name.

Params
  • kml as element()
Returns
  • map(xs:string,item()*)
declare function this:simple-meta($kml as element()) as map(xs:string,item()*)
{
  util:merge-into((
    map {},
    if (exists($kml/@id)) then map {"id": data($kml/@id)} else (),
    if (exists($kml/kml:name)) then map {"name": data($kml/kml:name)} else (),
    if (exists($kml/kml:description)) then map {"description": data($kml/kml:description)} else ()
  ))
}

Original Source Code

xquery version "3.1";
(:~
 : KML: parse to geometric objects
 : Pretty basic: 
 : Utterly oblivious to coordinate systems; you'll want to map to canvas space.
 : Treats altitude as z coordinate
 : Doesn't honour modifiers like extrude, altitudeMode, etc.
 : 
 :
 : Copyright© Mary Holstege 2023
 : CC-BY (https://creativecommons.org/licenses/by/4.0/)
 : @since June 2023
 : @custom:Status Bleeding edge
 :)
module namespace this="http://mathling.com/geometric/kml"; 

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 slot="http://mathling.com/type/slot"
       at "../types/slot.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 edge="http://mathling.com/geometric/edge"
       at "../geo/edge.xqy";
import module namespace box="http://mathling.com/geometric/box"
       at "../geo/box.xqy";
import module namespace ellipse="http://mathling.com/geometric/ellipse"
       at "../geo/ellipse.xqy";
import module namespace path="http://mathling.com/geometric/path"
       at "../geo/path.xqy";
import module namespace cpoly="http://mathling.com/geometric/complex-polygon"
       at "../geo/complex-polygon.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 namespace kml="http://www.opengis.net/kml/2.2";

(:~
 : parse()
 : Parse KML, pulling out Geometry elements and turning them into geometric
 : objects. Everything within a Folder will be combined into one slot, as
 : will MultiGeometry contents.
 :)
declare function this:parse(
  $kml as element(kml:kml),
  $parsemeta as function(element()) as map(xs:string,item()*)  
) as map(xs:string,item()*)*
{
  for $feature in $kml/(kml:Placemark|kml:Folder|kml:Document)
  return this:parse-feature($feature, $parsemeta)
};

declare function this:parse(
  $kml as element(kml:kml)
) as map(xs:string,item()*)*
{
  this:parse($kml, this:simple-meta#1)
};

(:~
 : parse-geometry()
 : Parse a Geometry KML element into geometric objects.
 :)
declare function this:parse-geometry(
  $kml as element(),
  $parsemeta as function(element()) as map(xs:string,item()*)  
) as map(xs:string,item()*)*
{
  (
    typeswitch($kml)
    case element(kml:Point) return (
      let $coordinates := tokenize(normalize-space(data($kml/kml:coordinates)),",")[. ne ""]
      return point:vector($coordinates!number(.))
    )
    case element(kml:LineString) return (
      let $points := tokenize(normalize-space(data($kml/kml:coordinates)),"[\p{Z}]+")[. ne ""]
      return (
        path:path(edge:to-edges(
          for $p in $points
          let $pcoords := tokenize($p,",")[. ne ""]
          return point:vector($pcoords!number(.))
        ))
      )
    )
    case element(kml:LinearRing) return (
      let $points := tokenize(normalize-space(data($kml/kml:coordinates)),"[\p{Z}]+")[. ne ""]
      return (
        path:polygon(edge:to-edges(
          for $p in $points
          let $pcoords := tokenize($p,",")[. ne ""]
          return point:vector($pcoords!number(.))
        ))
      )
    )
    case element(kml:Polygon) return (
      let $outer := this:parse-geometry($kml/kml:outerBoundaryIs/kml:LinearRing)
      let $inners := (
        for $inner in $kml/kml:innerBoundaryIs/kml:LinearRing
        return this:parse-geometry($inner)
      )
      return (
        cpoly:complex-polygon($outer, $inners)
      )
    )
    case element(kml:MultiGeometry) return (
      slot:slot(
        for $e in $kml/* return this:parse-geometry($e)
      )
    )
    (: case element(kml:Model) return () DAE/COLLADA file linked :)
    default return errors:error("GEOM-NOTIMPLEMENTED", ("parse-geometry", name($kml)))
  )=>geom:with-properties($parsemeta($kml))
}; (: parse-geometry :)

declare function this:parse-geometry(
  $kml as element()
) as map(xs:string,item()*)
{
  this:parse-geometry($kml, this:simple-meta#1)
};

(:~
 : parse-feature()
 : Parse a KML Feature element, pulling out Geometry elements and turning them
 : into geometric objects. 
 :)
declare function this:parse-feature(
  $kml as element(),
  $parsemeta as function(element()) as map(xs:string,item()*)  
) as map(xs:string,item()*)
{
  typeswitch($kml)
  case element(kml:Placemark) return (
    slot:slot(
      for $e in $kml/(kml:Point|kml:LineString|kml:LinearRing|kml:Polygon|kml:MultiGeometry)
      return this:parse-geometry($e),
      $parsemeta($kml)
    )
  )
  case element(kml:Folder) return (
    slot:slot(
      for $feature in $kml/(kml:Placemark|kml:Folder|kml:Document)
      return this:parse-feature($feature, $parsemeta),
      $parsemeta($kml)
    )
  )
  case element(kml:Document) return (
    slot:slot(
      for $feature in $kml/(kml:Placemark|kml:Folder|kml:Document)
      return this:parse-feature($feature, $parsemeta),
      $parsemeta($kml)
    )
  )
  default return errors:error("GEOM-NOTIMPLEMENTED", ("parse-feature", name($kml)))
};

declare function this:parse-feature(
  $kml as element()
) as map(xs:string,item()*)
{
  this:parse-feature($kml, this:simple-meta#1)
};

(:~
 : simple-meta()
 : Parsing function for metadata that just grabs the description as a string
 : as well as the id and name.
 :)
declare function this:simple-meta($kml as element()) as map(xs:string,item()*)
{
  util:merge-into((
    map {},
    if (exists($kml/@id)) then map {"id": data($kml/@id)} else (),
    if (exists($kml/kml:name)) then map {"name": data($kml/kml:name)} else (),
    if (exists($kml/kml:description)) then map {"description": data($kml/kml:description)} else ()
  ))
};