Include GPS position

I have the habit of recording sightings of remarkable species of birds, insects, plants etc along with their location.

Is it possible to record the GPS position when taking a note? I found a few references in this forum but they were either for Version 1 or otherwise not easy for me to understand how to implement them, nevertheless they were important in that they indicated that in principle this should be possible.
Any help would be much appreciated.

I made it work like this:

function js.func(args, body)
  -- load a js module inline that exposes an asycn function constructor
  local constructFunc = js.import([[data:text/javascript,
export const funcConstructor = (args, body) => new (async () => {}).constructor(args, body);
]]).funcConstructor
  -- construct the function with the specified argument list and body
  return constructFunc(args, body)
end

function js.run(code)
  return js.func("", code)()
end

-- Evaluate a JavaScript expression
function js.expr(code)
  return js.run("return (" .. code .. ")")
end

function currentGpsCoords()
  return js.run([[
    const getPos = () => new Promise((resolve, reject) =>
      navigator.geolocation.getCurrentPosition(resolve, reject, { timeout: 3_000 }));
    const { coords } = await getPos();
    return [coords.latitude, coords.longitude];
  ]])
end

function gspCoordsToNiceString(coords)
  return js.expr([[(coords) => {
    // copied from: https://stackoverflow.com/questions/5786025/decimal-degrees-to-degrees-minutes-and-seconds-in-javascript#5786281
    function convertDDToDMS(D, lng) {
      return {
        dir: D < 0 ? (lng ? "W" : "S") : lng ? "E" : "N",
        deg: 0 | (D < 0 ? (D = -D) : D),
        min: 0 | (((D += 1e-9) % 1) * 60),
        sec: (0 | (((D * 60) % 1) * 6000)) / 100,
      };
    }
    function convertDDToDMSString(D, lng) {
      const obj = convertDDToDMS(D, lng);
      return `${obj.deg}° ${obj.min}' ${obj.sec}" ${obj.dir}`
    }
    return coords.map((coord, i) => convertDDToDMSString(coord, i === 1)).join(" ");
  }]])(coords)
end

slashCommand.define {
  name = "gps",
  run = function()
    editor.insertAtCursor(gspCoordsToNiceString(currentGpsCoords()) .. "|^|", false, true)
  end
}

slashCommand.define {
  name = "gpsAttr",
  run = function()
    editor.insertAtCursor("[gps: [" .. table.concat(currentGpsCoords(), ", ") .. "]]|^|", false, true)
  end
}

By using an async function in JavaScript and awaiting the result of getCurrentPosition within this function it can return the current position (so there is no callback in lua necessary), which fixes the issue with this other attempt, however I also get the same error for chromium on desktop, on a smartphone it works for me. On the other hand I have not yet experienced the issue of the client becoming unresponsive.

Here an example for a Page Template:

---
description: "Creates a note for the current gps position"
tags: meta/template/page
suggestedName: "Inbox/${table.concat(currentGpsCoords(), ', '):gsub('%.', '_')}"
confirmName: false
openIfExists: true
command: "Note for GPS Position"
frontmatter: "gps: [${table.concat(currentGpsCoords(), ', ')}]"
---

create at: ${gspCoordsToNiceString(currentGpsCoords())}

|^|
1 Like

There are cleaner ways to do this via the native js.window "escape hatch", here is a starting point:

command.define {
  name = "Test Geo",
  run = function()
    js.window.navigator.geolocation.getCurrentPosition(function(result)
      js.log("Result", result)
    end, function(err) print("Error", err) end, {timeout=3000})
  end
}

This will print "Result" with your a Geo object. Hopefully you can take it from there.

That was used in the linked attempt, however for something like a slash command, I don't know how you would "synchronously" get the result of a callback using Lua. Which is why I used JavaScript to await a promise that resolves on the callback, and Lua apparently just awaits the result of an asynchronous JavaScript function (in this implementation). It obviously still is not synchronous but in Lua it behaves as though it was synchronous.

I guess there is also this option:

function awaitCallback(fn)
  return js.new(js.window.Promise, fn)
end

Which can then be used like this:

function currentGpsCoords()
  local result = awaitCallback(function(resolve, reject)
    js.window.navigator.geolocation.getCurrentPosition(resolve, reject, { timeout = 3000 })
  end)
  local coords = result.coords
  return { coords.latitude, coords.longitude }
end