Message Boards Message Boards

Exact GeoPosition from FormFunction

The form for the user: intuitive and easy to use

Backstory

When trying to set up a system to collect birdwatching data from the public, I was recently faced with the problem of collecting accurate locations from people filling in the form. The obvious choice is to use Here, but the accuracy, especially on laptops was just not enough.

I reached out to Christopher Wolfram and Carlo Barbieri for support and together (large parts of the code contained here is their doing) we were able to create an intuitive and functional solution that let us combine the FormFunction with embedded HTML code and JavaScript accessing the Google Maps API. Many things about the code can be adjusted, like whether you drag the map or the marker and many more, but it has already been extremely useful for my purpose, so I thought I should share with the community.

The code

First, we need to set up the function we are going to call on that consists of HTML and Javascript embedding the map.

    apiKey="Your_API_key";
    mapControl[apiKey_, name_, defaultPosition_, defaultZoom_:15]:= Function[EmbeddedHTML@StringTemplate["
    <input type=\"hidden\" id=\"mapField\" name=\"`Name`\">
    <div id=\"selectionMap\" style=\"width:100%;height:400px;\"></div>
    <script>
    field = document.getElementById(\"mapField\")
    function initMap() {
        defaultPosition = {lat: `Latitude`, lng: `Longitude`}
        var map = new google.maps.Map(
           document.getElementById(\"selectionMap\"),
           {zoom: `DefaultZoom`, center: defaultPosition})
        var marker = new google.maps.Marker({
           position: defaultPosition,
           draggable:false,
           map: map
        })
        field.value = marker.position
        google.maps.event.addListener(
           map,
           \"center_changed\",
           function() {
             marker.setPosition(map.getCenter())
             field.value = marker.position
           }
        )

    function showPosition(position) {
       coords = {
         lat: position.coords.latitude,
         lng: position.coords.longitude
       }
       marker.setPosition(coords)
       map.panTo(coords)
       field.value = marker.position
    }
    if(navigator.geolocation) {
       navigator.geolocation.getCurrentPosition(showPosition, console.log, {enableHighAccuracy: true});
    }
}
</script>
<script async defer src=\"https://maps.googleapis.com/maps/api/js?key=`apiKey`&callback=initMap\">
</script>
"][<|"Name"->name,"Latitude"->defaultPosition[[1,1]],"Longitude"->defaultPosition[[1,2]],"DefaultZoom"->defaultZoom,"apiKey"-> apiKey|>]]


Then we deploy a form and use the function as the control for one of the inputs.

CloudDeploy[FormFunction[{"location"-><|
    "Label"->"Drag the map so the marker points at your location",
    "Interpreter"->"StructuredGeoCoordinates",
    "Control"->mapControl[apiKey,"location",Entity["City",{"Champaign","Illinois","UnitedStates"}]["Position"]]|>},
    {#location,GeoGraphics[GeoMarker[#location],GeoRange->Quantity[1,"Miles"]]}&],
    "https://www.wolframcloud.com/objects/3550411e-7493-4e1f-811e-8a1be25c1f38",
    Permissions->"Public"]**strong text**

The finished product

The resulting section of the form is intuitive and easy to use. It will point at the position given by GPS if the user gives permissions and the default position I set otherwise.

The form for the user: intuitive and easy to use

what the form returns is my exact location

You can see for yourself and find out your exact geolocation here:

https://www.wolframcloud.com/objects/3550411e-7493-4e1f-811e-8a1be25c1f38

6 Replies

Hi Katja,

Nice work! I am not able to view the cloud file. Please change the access permission to "public".

POSTED BY: Adiba Shaikh

Slow to find this, but… why is this not possible in WL itself? As a simple example, a control that is a ClickPane with a map as the background? In truth I'm having trouble understanding how arbitrary control functions return a value to the form.

POSTED BY: Gareth Russell

And here is the extension to do the same thing using OpenStreetMap and Leaflet.js:

mapControl[name_, defaultPosition_, defaultZoom_:15]:=Function[EmbeddedHTML@StringTemplate["
<input type=\"hidden\" id=\"mapField\" name=\"`Name`\">
<link
rel=\"stylesheet\"
href=\"https://unpkg.com/leaflet@1.3.4/dist/leaflet.css\"
/>

<div id=\"selectionMap\" style=\"width:400px;height:400px;\"></div>

<script
src=\"https://unpkg.com/leaflet@1.3.4/dist/leaflet.js\">
</script>

<script>
field = document.getElementById(\"mapField\")
defaultPosition = [`Latitude`,`Longitude`]
var map = L.map(\"selectionMap\").setView(defaultPosition, `DefaultZoom`);
field.value=[map.getCenter().lat,map.getCenter().lng]
mapLink ='<a href=\"https://openstreetmap.org\">OpenStreetMap</a>';
L.tileLayer(
    'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: 'Map data &copy;'  + mapLink,
    maxZoom: 18
    }).addTo(map);
var marker = L.marker(defaultPosition,
{draggable: false})
.addTo(map);

map.on('move',function(ev){
    field.value=[map.getCenter().lat,map.getCenter().lng]
    marker.setLatLng(map.getCenter())
})
if(navigator.geolocation) {
    navigator.geolocation.getCurrentPosition(function(p){map.panTo([p.coords.latitude,p.coords.longitude])}, 
    console.log, {enableHighAccuracy: true});
}

</script>

"][<|"Name"-> name, "Latitude"->defaultPosition[[1,1]],"Longitude"->defaultPosition[[1,2]],"DefaultZoom"->defaultZoom|>]]

Deploying it:

CloudDeploy[FormFunction[{"location"-><|
    "Label"->"Drag the map so the marker points at your location",
    "Interpreter"->"StructuredGeoCoordinates",
    "Control"->mapControl["location",Entity["City",{"Champaign","Illinois","UnitedStates"}]["Position"]]|>},
    {#location,GeoGraphics[GeoMarker[#location],GeoRange->Quantity[1,"Miles"]]}&],
    "https://www.wolframcloud.com/objects/b0aa199f-a39b-446f-b19b-53ccd4a14847",Permissions->"Public"]

The new form: the form using open street map The new result: the new return image The link to the OSM form: https://www.wolframcloud.com/objects/b0aa199f-a39b-446f-b19b-53ccd4a14847

Thank you for making this a staff pick and pointing out the permissions problem. I fixed it and improved the landing site for the FormFunction.

enter image description here - Congratulations! This post is now a Staff Pick as distinguished by a badge on your profile! Thank you, keep it coming, and consider contributing your work to the The Notebook Archive! We added your image also to the top for leading above the fold graphics. You might want to consider using Permissions -> "Public" in CloudDeploy as currently the content at URL you has given at the end cannot be viewed by general public.

POSTED BY: EDITORIAL BOARD

@Christopher Wolfram @Carlo Barbieri Thank you guys again for making this possible. It has been serving me and the Korean birds well :)

Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract