2

My XML document contains a record of the movement of an object. The XML document consists of a series of observations. Each observation contains the lat/long location of the object, the lat/long of the observing sensor, and a date/time stamp.

<Track-History>
    <Track-ID>XYZ</Track-ID>
    <Observation>
        <Target-Latitude>10.3</Target-Latitude>
        <Target-Longitude>20.8</Target-Longitude>
        <Observer-Latitude>40.0</Observer-Latitude>
        <Observer-Longitude>50.0</Observer-Longitude>
        <DateTime>20230202T071700.00</DateTime>
    </Observation>
    <Observation>
        <Target-Latitude>15.1</Target-Latitude>
        <Target-Longitude>25.2</Target-Longitude>
        <Observer-Latitude>40.0</Observer-Latitude>
        <Observer-Longitude>50.0</Observer-Longitude>
        <DateTime>20230202T071800.00</DateTime>
    </Observation>
</Track-History>

I want to fuzz the locations of the object by rounding the decimal lat and long values. So I created this updating function:

declare updating function f:fuzzPoint($lat as element(), $long as element())
{
    replace node $lat with 
        element {name($lat)} {round(number(data($lat)))},
    replace node $long with 
        element {name($long)} {round(number(data($long)))}
};

When I invoke that function:

{f:fuzzPoint($obs/Target-Latitude, $obs/Target-Longitude)}

I get this error message:

If any subexpression is updating, then all must be updating

Oxygen XML gives a red squiggly line under the function arguments, so apparently the arguments must be updating. Yes? If so, how to make the arguments updating?

Below is my complete XQuery Update program.

declare namespace f = "function";

declare variable $Track-History := doc('Track-History.xml');
declare variable $track-history-points := (for $i in $Track-History//Observation return [$i/Target-Latitude, $i/Target-Longitude]);
declare variable $AOR := (); (: should be a sequence of points, fake it for now :)

declare function f:isInside($points, $polygon) as xs:boolean
{
    true()
};

declare updating function f:fuzzPoint($lat as element(), $long as element())
{
    replace node $lat with 
        element {name($lat)} {round(number(data($lat)))},
    replace node $long with 
        element {name($long)} {round(number(data($long)))}
};

if (f:isInside($track-history-points, $AOR)) then
    for $obs in $Track-History//Observation return
      replace node $obs with
      <Observation>
            {f:fuzzPoint($obs/Target-Latitude, $obs/Target-Longitude)}
            {$obs/Observer-Latitude}
            {$obs/Observer-Longitude}
            {$obs/DateTime}
       </Observation>
else
   replace node $Track-History/* with
      <Track-History/>
Roger Costello
  • 3,007
  • 1
  • 22
  • 43
  • I think you can't have something using XQuery update inside of the `with` clause of a `replace` update, there you are only allowed simple, non updating expressions. – Martin Honnen Feb 02 '23 at 17:41

2 Answers2

3

Inside an update expression – replace node $obs with ... – it’s not allowed to perform other updates. The code works if you modify f:fuzzPoint such that it returns the new element nodes:

declare function f:fuzzPoint($lat as element(), $long as element()) {
  element { name($lat) } { round(number(data($lat))) },
  element { name($long) } { round(number(data($long))) }
};

The updating keyword in the function declaration makes sense if you move the update operation inside the function body:

declare updating function f:replace($obs as element()) {
  let $lat := $obs/Target-Latitude
  let $long := $obs/Target-Longitude
  return replace node $obs with <Observation>{
    element { name($lat) } { round(number(data($lat))) },
    element { name($long) } { round(number(data($long))) },
    $obs/Observer-Latitude,
    $obs/Observer-Longitude,
    $obs/DateTime
  }</Observation>
};

if (f:isInside($track-history-points, $AOR)) then
  for $obs in $Track-History//Observation return f:replace($obs)
else
   replace node $Track-History/* with
      <Track-History/>
Christian Grün
  • 6,012
  • 18
  • 34
1

I wonder whether

declare namespace f = "function";

declare variable $Track-History := doc('Track-History.xml');
declare variable $track-history-points := (for $i in $Track-History//Observation return [$i/Target-Latitude, $i/Target-Longitude]);
declare variable $AOR := (); (: should be a sequence of points, fake it for now :)

declare function f:isInside($points, $polygon) as xs:boolean
{
    true()
};

declare function f:fuzzValue($v as xs:double) as xs:double
{
  round($v)
};


if (f:isInside($track-history-points, $AOR)) then
    for $lv in $Track-History//Observation/(Target-Latitude, Target-Longitude) 
    return
      replace value of node $lv  
      with f:fuzzValue($lv)

else
   replace node $Track-History/* with
      <Track-History/>

is all you want to do there.

Martin Honnen
  • 160,499
  • 6
  • 90
  • 110
  • That's a good idea Martin. However, when I go to actually implement the fuzz function (what I showed is a too simplistic algorithm for fuzzing), fuzzing will require both lat and long values. That is, fuzzing latitude depends on longitude and vice versa. – Roger Costello Feb 02 '23 at 18:15