Files
asterisk/res/res_geolocation/eprofile_to_pidf.xslt
George Joseph 445130eeab res_geolocation: Fix multiple issues with XML generation.
* 3d positions were being rendered without an enclosing `<gml:pos>`
  element resulting in invalid XML.
* There was no way to set the `id` attribute on the enclosing `tuple`, `device`
  and `person` elements.
* There was no way to set the value of the `deviceID` element.
* Parsing of degree and radian UOMs was broken resulting in them appearing
  outside an XML element.
* The UOM schemas for degrees and radians were reversed.
* The Ellipsoid shape was missing and the Ellipse shape was defined multiple
  times.
* The `crs` location_info parameter, although documented, didn't work.
* The `pos3d` location_info parameter appears in some documentation but
  wasn't being parsed correctly.
* The retransmission-allowed and retention-expiry sub-elements of usage-rules
  were using the `gp` namespace instead of the `gbp` namespace.

In addition to fixing the above, several other code refactorings were
performed and the unit test enhanced to include a round trip
XML -> eprofile -> XML validation.

Resolves: #1667

UserNote: Geolocation: Two new optional profile parameters have been added.
* `pidf_element_id` which sets the value of the `id` attribute on the top-level
  PIDF-LO `device`, `person` or `tuple` elements.
* `device_id` which sets the content of the `<deviceID>` element.
Both parameters can include channel variables.

UpgradeNote: Geolocation: In order to correct bugs in both code and
documentation, the following changes to the parameters for GML geolocation
locations are now in effect:
* The documented but unimplemented `crs` (coordinate reference system) element
  has been added to the location_info parameter that indicates whether the `2d`
  or `3d` reference system is to be used. If the crs isn't valid for the shape
  specified, an error will be generated. The default depends on the shape
  specified.
* The Circle, Ellipse and ArcBand shapes MUST use a `2d` crs.  If crs isn't
  specified, it will default to `2d` for these shapes.
  The Sphere, Ellipsoid and Prism shapes MUST use a `3d` crs. If crs isn't
  specified, it will default to `3d` for these shapes.
  The Point and Polygon shapes may use either crs.  The default crs is `2d`
  however so if `3d` positions are used, the crs must be explicitly set to `3d`.
* The `geoloc show gml_shape_defs` CLI command has been updated to show which
  coordinate reference systems are valid for each shape.
* The `pos3d` element has been removed in favor of allowing the `pos` element
  to include altitude if the crs is `3d`.  The number of values in the `pos`
  element MUST be 2 if the crs is `2d` and 3 if the crs is `3d`.  An error
  will be generated for any other combination.
* The angle unit-of-measure for shapes that use angles should now be included
  in the respective parameter.  The default is `degrees`. There were some
  inconsistent references to `orientation_uom` in some documentation but that
  parameter never worked and is now removed.  See examples below.
Examples...
```
  location_info = shape="Sphere", pos="39.0 -105.0 1620", radius="20"
  location_info = shape="Point", crs="3d", pos="39.0 -105.0 1620"
  location_info = shape="Point", pos="39.0 -105.0"
  location_info = shape=Ellipsoid, pos="39.0 -105.0 1620", semiMajorAxis="20"
                semiMinorAxis="10", verticalAxis="0", orientation="25 degrees"
  pidf_element_id = ${CHANNEL(name)}-${EXTEN}
  device_id = mac:001122334455
  Set(GEOLOC_PROFILE(pidf_element_id)=${CHANNEL(name)}/${EXTEN})
```
2026-01-05 12:45:04 +00:00

244 lines
7.6 KiB
HTML

<?xml version="1.0"?>
<xsl:stylesheet version="1.1"
xmlns:ca="urn:ietf:params:xml:ns:pidf:geopriv10:civicAddr"
xmlns:dm="urn:ietf:params:xml:ns:pidf:data-model"
xmlns:fn="http://www.w3.org/2005/xpath-functions"
xmlns:gbp="urn:ietf:params:xml:ns:pidf:geopriv10:basicPolicy"
xmlns:gml="http://www.opengis.net/gml"
xmlns:gp="urn:ietf:params:xml:ns:pidf:geopriv10"
xmlns:gs="http://www.opengis.net/pidflo/1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:con="urn:ietf:params:xml:ns:geopriv:conf"
xmlns:date="http://exslt.org/dates-and-times">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:param name="suppress_empty_ca_elements" select="false()"/>
<!-- REMINDER: The "match" and "select" xpaths refer to the input document,
not the output document -->
<xsl:template match="presence">
<!-- xslt will take care of adding all of the namespace declarations
from the list above -->
<presence xmlns="urn:ietf:params:xml:ns:pidf" entity="{@entity}">
<xsl:apply-templates/>
</presence>
</xsl:template>
<xsl:template match="person|device">
<xsl:element name="dm:{local-name(.)}">
<xsl:if test="@id">
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
</xsl:if>
<gp:geopriv>
<xsl:apply-templates select="./location-info"/>
<xsl:apply-templates select="./usage-rules"/>
<xsl:apply-templates select="./method"/>
<xsl:apply-templates select="./note-well"/>
</gp:geopriv>
<xsl:if test="./timestamp">
<dm:timestamp>
<xsl:value-of select="./timestamp"/>
</dm:timestamp>
</xsl:if>
<xsl:if test="./deviceID">
<dm:deviceID>
<xsl:value-of select="./deviceID"/>
</dm:deviceID>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="tuple">
<xsl:element name="tuple" namespace="urn:ietf:params:xml:ns:pidf">
<xsl:if test="@id">
<xsl:attribute name="id"><xsl:value-of select="@id"/></xsl:attribute>
</xsl:if>
<xsl:element name="status" namespace="urn:ietf:params:xml:ns:pidf">
<gp:geopriv>
<xsl:apply-templates select="./location-info"/>
<xsl:apply-templates select="./usage-rules"/>
<xsl:apply-templates select="./method"/>
<xsl:apply-templates select="./note-well"/>
</gp:geopriv>
</xsl:element>
<xsl:if test="./timestamp">
<xsl:element name="timestamp" namespace="urn:ietf:params:xml:ns:pidf">
<xsl:value-of select="./timestamp"/>
</xsl:element>
</xsl:if>
<xsl:if test="./deviceID">
<dm:deviceID>
<xsl:value-of select="./deviceID"/>
</dm:deviceID>
</xsl:if>
</xsl:element>
</xsl:template>
<xsl:template match="location-info">
<gp:location-info>
<xsl:apply-templates/>
</gp:location-info>
</xsl:template>
<!-- When we're using the civicAddress format, the translation is simple.
We add gp:location-info and ca:civicAddress, then we just copy in
each element, adding the "ca" namespace -->
<xsl:template match="civicAddress/*">
<xsl:if test="not($suppress_empty_ca_elements) or boolean(node())">
<xsl:element name="ca:{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:if>
</xsl:template>
<xsl:template match="location-info/civicAddress">
<ca:civicAddress xml:lang="{@lang}">
<xsl:apply-templates/>
</ca:civicAddress>
</xsl:template>
<!-- All GML shapes share common processing for the "srsName" attribute -->
<xsl:template name="shape">
<xsl:choose>
<xsl:when test="@crs = '3d'">
<xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4979</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="srsName">urn:ogc:def:crs:EPSG::4326</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- The GML shapes themselves. They don't all have the same namespace unfortunately... -->
<xsl:template match="Point|Circle|Ellipse|ArcBand|Sphere|Ellipsoid">
<xsl:variable name="namespace">
<xsl:choose>
<xsl:when test="name() = 'Point'">
<xsl:value-of select="'gml'"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="'gs'"/>
</xsl:otherwise>
</xsl:choose>
</xsl:variable>
<xsl:element name="{$namespace}:{name()}">
<xsl:call-template name="shape"/>
<xsl:apply-templates select="./*"/>
</xsl:element>
</xsl:template>
<!-- ... and some are more complex than others. -->
<xsl:template match="Polygon">
<gml:Polygon>
<xsl:call-template name="shape"/>
<gml:exterior>
<gml:LinearRing>
<xsl:apply-templates select="./pos|posList"/>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</xsl:template>
<!-- Prism with a Polygon and height -->
<xsl:template match="Prism">
<gs:Prism>
<xsl:call-template name="shape"/>
<gs:base>
<gml:Polygon>
<gml:exterior>
<gml:LinearRing>
<xsl:apply-templates select="./pos|posList"/>
</gml:LinearRing>
</gml:exterior>
</gml:Polygon>
</gs:base>
<xsl:apply-templates select="./height"/>
</gs:Prism>
</xsl:template>
<!-- method has no children so we add the "gp" namespace and copy in the value -->
<xsl:template match="method">
<gp:method>
<xsl:value-of select="."/>
</gp:method>
</xsl:template>
<!-- note-well has no children so we add the "gp" namespace and copy in the value -->
<xsl:template match="note-well">
<gp:note-well>
<xsl:value-of select="."/>
</gp:note-well>
</xsl:template>
<!-- usage-rules does have children so we add the "gp" namespace and copy in
the children, also adding the "gbp" namespace -->
<xsl:template match="usage-rules">
<gp:usage-rules>
<xsl:for-each select="*">
<xsl:element name="gbp:{local-name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:for-each>
</gp:usage-rules>
</xsl:template>
<!-- These are the GML format primitives -->
<xsl:template name="name-value">
<xsl:element name="gml:{name()}">
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template name="length">
<xsl:element name="gs:{name()}">
<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9001</xsl:attribute>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<xsl:template name="angle">
<xsl:element name="gs:{name()}">
<xsl:choose>
<xsl:when test="@uom = 'radians'">
<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9101</xsl:attribute>
</xsl:when>
<xsl:otherwise>
<xsl:attribute name="uom">urn:ogc:def:uom:EPSG::9102</xsl:attribute>
</xsl:otherwise>
</xsl:choose>
<xsl:value-of select="."/>
</xsl:element>
</xsl:template>
<!-- These are the GML shape parameters -->
<xsl:template match="orientation"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="radius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="height"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="semiMajorAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="semiMinorAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="verticalAxis"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="innerRadius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="outerRadius"><xsl:call-template name="length" /></xsl:template>
<xsl:template match="startAngle"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="openingAngle"><xsl:call-template name="angle" /></xsl:template>
<xsl:template match="pos"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template match="posList"><xsl:call-template name="name-value" /></xsl:template>
<xsl:template match="confidence">
<con:confidence pdf="{@pdf}">
<xsl:value-of select="."/>
</con:confidence>
</xsl:template>
</xsl:stylesheet>