Welcome to the LimeSurvey Community Forum

Ask the community, share ideas, and connect with other LimeSurvey users!

Can survey participants directly mark specific locations on the map?

  • Hyeran
  • Hyeran's Avatar Topic Author
  • Offline
  • New Member
  • New Member
More
6 months 1 week ago #251290 by Hyeran
Hello. I am planning to integrate OpenStreetMap into a survey, allowing participants to directly indicate areas of discomfort within City A (Example).
I am reaching out to inquire if this functionality is possible in LimeSurvey.
  1. Is it feasible to implement the mentioned feature in LimeSurvey? It would be sufficient if we could obtain latitude and longitude for specific locations.
  2. If the first question is achievable in LimeSurvey, is there a method to enable multiple selections for several locations within a single question?
Thank you!

Please Log in to join the conversation.

  • tpartner
  • tpartner's Avatar
  • Offline
  • LimeSurvey Community Team
  • LimeSurvey Community Team
More
6 months 1 week ago - 6 months 1 week ago #251297 by tpartner
There is no core LimeSurvey facility to record multiple locations from a map but I have been working on this for a new custom question theme, using ArcGIS Esri Leaflet .

Add the following three blocks of code to the source of a multiple-short-text question. It will insert a Leaflet map and allow dropping of multiple location markers. When a marker is dropped, its coordinates and Geolocation data are recorded in the question text inputs. 

In lines 20-30 of the JavaScript, you will need to replace "yourAPIKey" with an ArcGIS API key and adjust the map parameters to your requirements.

1) Links to the various Leaflet files:
Code:
<!-- Load Leaflet from CDN -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" crossorigin="" />
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" crossorigin=""></script>
 
<!-- Load Esri Leaflet from CDN -->
<script src="https://unpkg.com/esri-leaflet@3.0.10/dist/esri-leaflet.js"></script>
<script src="https://unpkg.com/esri-leaflet-vector@4.0.1/dist/esri-leaflet-vector.js"></script>
 
<!-- Load Esri Leaflet Geocoder from CDN -->
<link rel="stylesheet" href="https://unpkg.com/esri-leaflet-geocoder@^3.1.3/dist/esri-leaflet-geocoder.css">
<script src="https://unpkg.com/esri-leaflet-geocoder@^3.1.3/dist/esri-leaflet-geocoder.js"></script>

2) Some styles:
Code:
<style type="text/css" data-author="Tony Partner">
 
  .custom-map-outer-container .custom-map {
    max-width: 100%;
  }
 
  .custom-map-outer-container .leaflet-popup-content {
    line-height: 1.3;
    font-size: 13px;
  }
 
  .custom-map-outer-container .leaflet-popup-content p {
    margin: 0 0 10px 0;
  }
 
  .custom-map-outer-container .leaflet-popup-content p:last-child {
    margin: 0 0 0 0;
  }
 
  @media only screen and (max-width: 400px) {
 
    .custom-map-outer-container .leaflet-popup-content {
      max-width: 200px;
    }
  }
</style>

3) The required JavaScript:
Code:
<script type="text/javascript" data-author="Tony Partner">
 
  $(document).on('ready pjax:scriptcomplete',function(){  
 
    // Your Your Esri Leaflet (ArcGIS) API key
    var apiKey = 'yourAPIKey';
 
    // Initial map parameters
    var position = '51.5072 -0.1279'; // Lat/Long coordinates, separated by a space
    var zoom = 12;
    var mapWidth = '800px';
    var mapHeight = '400px';
 
    // Some text definitions
    var removeText = 'Remove marker';
    var warningText = 'Only 6 markers are allowed. You must remove a marker before adding another.';
 
    //// NO EDITING REQUIRED BELOW HERE ////
 
    var qID = {QID};
    var thisQuestion = $('#question'+qID);
 
    thisQuestion.addClass('custom-map-question');
 
    // Disable the text inputs
    $(':text.form-control', thisQuestion).prop('readonly', true);
 
    // Insert a map wrapper
    $('.answer-container', thisQuestion).prepend('<div class="custom-map-outer-container">\
        <div class="custom-map" id="custom-map-'+qID+'" style="width:'+mapWidth+'; height:'+mapHeight+';">  </div>\
      </div>');
 
    // Initiate the map
    const basemapEnum = "ArcGIS:Navigation";
    const map = L.map('custom-map-'+qID+'', {
      minZoom: 2
    });  
 
    var coordsArr = $.trim(position).split(' ');
    var latAtt = Number($.trim(coordsArr[0]));
    var lngAtt = Number($.trim(coordsArr[1]));
    mapLat = latAtt;
    mapLng = lngAtt;
 
    map.setView([mapLat, mapLng], zoom);
 
    L.esri.Vector.vectorBasemapLayer(basemapEnum, {
      apiKey: apiKey
    }).addTo(map);
 
    // Add a LayerGroup to the map to contain the markers and geocoding results. 
    const markerLayer = L.layerGroup().addTo(map);
 
    // An array to hold the markers (so we can loop through them later)
    var markers = [];
 
    // A function to insert markers and pop-ups
    function createMarker(coords, latLngString, addressString, newMarker) {
 
      var id;
      if (markers.length < 1) {
        //id = Number(qID)+0;
        id = qID+'_'+0;
      }
      else {
        const lastIDNum = Number(markers[markers.length - 1]._markerID.split('_')[1]);
        const newIDNum = lastIDNum + 1;
        //id = (markers[markers.length - 1]._markerID + 1);
        id = qID+'_'+newIDNum;
      }
 
      // The marker pop-up content
      var popupContent = '<p>'+latLngString+', '+addressString+'</p>\
        <p><button type="button" class="remove-marker btn btn-sm btn-primary" data-marker-id="'+id+'">Remove</button></p>';
 
      // Define and insert a marker and associated pop-up
      myMarker = L.marker(coords, {
        draggable: false
      });
      myMarker._markerID = id;
      myMarker._address_string = addressString;
      var myPopup = myMarker.bindPopup(popupContent, {
        //closeButton: false
      });
      map.addLayer(myMarker);
 
      // Add the marker to the array
      markers.push(myMarker);
 
      // This is a new user-created marker
      if(newMarker == true) {
        myMarker.openPopup();
        loadMapData();
      }
    }
 
    // A function to remove markers
    function clearMarker(id) {
 
      var new_markers = [];
 
      // Loop through the markers
      markers.forEach(function(marker) {
        // Remove it if the ID matches
        if (marker._markerID == id) {
          map.removeLayer(marker)
        }
        // Otherwise add it to the new array
        else {
          new_markers.push(marker)
        }
      })
 
      // Update the original markers array
      markers = new_markers;
 
      // Load the LS data
      loadMapData();
    }
 
    // Listener on "Remove" buttons
    $('body').on('click', 'button.remove-marker', function (e) {
      clearMarker($(this).attr('data-marker-id'));
    });
 
    // Listener on map for reverse geocoding
    map.on('click', function (e) {
 
      if(markers.length < $(':text.form-control', thisQuestion).length) {
        L.esri.Geocoding
          .reverseGeocode({
            apikey: apiKey
          })
          .latlng(e.latlng)
 
          .run(function (error, result) {
            if (error) {
              return;
            }
 
            const latLngString = Math.round(result.latlng.lat * 100000) / 100000+', '+Math.round(result.latlng.lng * 100000) / 100000;
 
            createMarker(result.latlng, latLngString, result.address.LongLabel, true);
          });
      }
      else {
        // To-do - this could be a modal
        alert(warningText);
      }
    });
 
    // A function to load the results into LimeSurvey
    function loadMapData() {
      // Clear everything
      $(':text.form-control', thisQuestion).val('').trigger('keyup');
 
      // Loop through the markers
      $.each(markers, function(i, marker) {
        const latLngString = this._latlng.lat+', '+this._latlng.lng;
        const addressString = this._address_string;
 
        // Insert the LS data
        $(':text.form-control:eq('+i+')', thisQuestion).val(latLngString+' || '+addressString).trigger('keyup');
      })
    }
 
    // Returning to the page        
    $(':text.form-control', thisQuestion).each(function(i) {
      if($.trim($(this).val()) != '') {
        var dataCoords = $.trim($(':text.form-control:eq('+i+')', thisQuestion).val().split('||')[0]).split(',');
        var dataAddress = $.trim($(':text.form-control:eq('+i+')', thisQuestion).val().split('||')[1]);  
        var dataLatLngString = Math.round($.trim(dataCoords[0]) * 100000) / 100000+', '+Math.round($.trim(dataCoords[1]) * 100000) / 100000;
        var dataMarkerCoords =  { 'lat': $.trim(dataCoords[0]), 'lng':  $.trim(dataCoords[1]) };
 
        createMarker(dataMarkerCoords, dataLatLngString, dataAddress, false);
      }
    });
 
    if(markers.length > 0) {  
      // Zoom to fit all markers
      var markerBoundsGroup = new L.featureGroup(markers);
      map.fitBounds(markerBoundsGroup.getBounds());
    }
  });
</script>

 

Sample survey attached:  

File Attachment:

File Name: limesurvey...9852.lss
File Size:36 KB

Cheers,
Tony Partner

Solutions, code and workarounds presented in these forums are given without any warranty, implied or otherwise.
Last edit: 6 months 1 week ago by tpartner.
The following user(s) said Thank You: DenisChenu, Hyeran

Please Log in to join the conversation.

  • Hyeran
  • Hyeran's Avatar Topic Author
  • Offline
  • New Member
  • New Member
More
6 months 1 week ago #251298 by Hyeran
wow, thank you so much for the detailed explanation!

Please Log in to join the conversation.

  • DenisChenu
  • DenisChenu's Avatar
  • Offline
  • LimeSurvey Community Team
  • LimeSurvey Community Team
More
6 months 1 week ago #251309 by DenisChenu
@tpartner always rock !

I'm an admirer

Assistance on LimeSurvey forum and LimeSurvey core development are on my free time.
I'm not a LimeSurvey GmbH member, professional service on demand , plugin development .
I don't answer to private message.

Please Log in to join the conversation.

  • Hyeran
  • Hyeran's Avatar Topic Author
  • Offline
  • New Member
  • New Member
More
6 months 1 week ago #251339 by Hyeran
Tony, thanks to your explanation I was able to put the map inside the survey.

Is it possible to have a specific city (e.g. Bamberg, Germany) appear instead of London when the map first appears?

Please Log in to join the conversation.

  • Hyeran
  • Hyeran's Avatar Topic Author
  • Offline
  • New Member
  • New Member
More
6 months 1 week ago #251343 by Hyeran
Sorry. I can't erase the answer I wrote before, so I'm leaving this comment. I solved the problem!

Please Log in to join the conversation.

  • tpartner
  • tpartner's Avatar
  • Offline
  • LimeSurvey Community Team
  • LimeSurvey Community Team
More
6 months 1 week ago - 6 months 1 week ago #251344 by tpartner
The "position" variable (line 9 of the JavaScript) determines the initial location of the map.

Change that to something like "49.9032 10.8952".

 

Cheers,
Tony Partner

Solutions, code and workarounds presented in these forums are given without any warranty, implied or otherwise.
Last edit: 6 months 1 week ago by tpartner.

Please Log in to join the conversation.

Lime-years ahead

Online-surveys for every purse and purpose