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.
You can see for yourself and find out your exact geolocation here:
https://www.wolframcloud.com/objects/3550411e-7493-4e1f-811e-8a1be25c1f38