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/)
Status: Bleeding edge
Imports
http://mathling.com/geometric/pathimport 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()*)*
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()*)*
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()*)*
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()*)
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()*)
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()*)
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()*)
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 () )) };