How to make a form which searches an item around a specific radius using google maps API?
Asked Answered
H

3

10

I am working on a website in which I want to make a circle on google map either around current location or some manual address.

  • Users will have option to decide whether they want to make circle around current location or some random address they will provide. (Users would have the option to put manual address inside current location as shown below in an image)

  • Now we also need to make sure that circle is of particular radius (0-20/70km from the current location) as well and user needs to decide that as well. (The line beneath the current location will decide the radius which users can move here and there 0-70km)

For example: user want to create a circle from current location till 30KM or user want to create a circle from some random address till 20KM.

enter image description here

The HTML code which I have used in order to make a search bar for search radius is:

<div class="input-searchradius">
   <input class="form-control search_radius mb-4" type="text" placeholder="search radius">
</div>



Problem Statement:

(1) I am wondering what changes I need to make or code I need to add so that the items are being searched around a specific radius. I think, I need to integrate the code Google Maps circle but I am not sure how I can do that.

(2) On hit of search radius on the website the following options/screen will appear at the bottom:

enter image description here

Homologize answered 27/7, 2018 at 15:18 Comment(10)
Hm I do not get the popup. But I can't help you with this I think, It is something like Google Maps API you want to build, but I am not that familiar with radius etc.Helles
1. Let us suppose I am looking for a calculator in what are you looking for.. placeholder 2. On click of search radius placeholder, it will ask for a geographic location (for the item being searched say calculator) of around 0-70 km from our current location (or the address which we manually put there). Forget about the popup for now. Let focus on the functionality.Homologize
@user5447339 check Google Map: Drawing Tool or Google Map: circle. Once the user fills out the form (location, radius), put one marker at the location of the gmap, then draw one circle based on radius value.Kittenish
@Kittenish can you give me a pointer how to proceed on this question ? I have never worked with google api before.Homologize
I am not familiar with Google MAP API. You can follow the guide of Google Map to reach your goal first.if any, Stackoverflow will be your home. :)Kittenish
@Kittenish No problem, thanks for letting me know. Surely, you have helped me a lot in my previous SO questions. I am wondering if there is anything I need to change in my question so that I can expect my answer soon. Till now, no answer.Homologize
Duplicate of #16561796Alyss
Something like this?Willywillynilly
@Willywillynilly Yes very close but I am not sure how I can integrate in the live code.Homologize
@Willywillynilly can you give me a pointer how to proceed in this question ?Homologize
G
6

Let's try to give you some first steps, I would no code the whole app, but rather give you some guide lines in to how to solve the small subproblems that you have:

Adding a circle in a map

Well, for this you have many different options for input, but the most important part is the addCircle function:

function addCircle(center){
    circle = new google.maps.Circle({
        map: map, //The existing map
        center: center,
        radius: 200, //This will be modified afterwards
        zindex: 100
    });
}

The center could come from a click for example:

// Area is wherever you want to attach the click, either a polygon, a map...
google.maps.event.addListener(area, "click", function(event) {
        addCircle(event.latLng);
});

OR by getting the position of a certain address (This is documented as well), OR whatever method (drag and drop the circle, drag the marker blablabla)

Adding a dynamic radius

Well, if we know that the radius of the Circle is given in meters, then is very easy to give the addCircle function the correct radius. For example, 20km -> 20 000 meters. So you just have to be able to access radius when calling addCircle (it can be an argument, a global variable... your choice).

We are done with the drawing part, lets now search within that circle.

Getting only the markers inside the circle

There is a prerequisite here, to have all the markers of your map. You may have an array of places that you get from a database or maybe you get the markers from Google Maps API (Place search for example).

After that you will have to compute the distance between those markers and your given center, and check if the distance is smaller than your radius (with computeDistanceBetween is very easy), so you will know which markers are valid for you.

const markers = [//array of my valid markers with position];
markers.filter( (marker) => 
    google.maps.geometry.spherical.computeDistanceBetween(marker.getPosition(), center.getPosition()) < radius; // Filter the markers which distance is bigger than radius;

The rest of the job should be as easy, place the markers in the map and do whatever you like with this information.

EXTRAS

As a further help there are a pair of examples/answers that may be useful for you:

Full Google Map API example, very easy step by step guide.

Radius search using places, a good answer in to how to do radius search.

Example of radius search, open F12 and debug the code if you like, but it is easy to follow.

EDIT**: I did not realize that 2 of these link where also pointed out in the comments.

Gyrostatic answered 31/7, 2018 at 8:15 Comment(2)
Thanks @Gyrostatic I have a similar question. I am wondering if you can have a look.Homologize
Give me few minutes/hours and I will take a look :)Gyrostatic
Z
1

I recommend using a worker thread to do the searching to free up the UI thread by doing the searching in the background. This is also useful if the user moves the circle or expands/contracts it as the previous search/render of matching markers can be abandoned,

importScripts("Tier3Toolbox.js");

var currVintage = 0;
var inBounds = false;
var facFilter = [];
var imageProlog = "<div style='height:5em; width:5em; display:inline-block;vertical-align:middle;'>" +
                  "<img style='height:100%; width: 100%; max-height:100%; max-width:100%' src='";
var imageEpilog = "' ></div>";
var facilityTable, lineBreak;

self.addEventListener('message', function(e) 
{
  var data = e.data;
  switch (data.cmd) {
    case 'init':
      initThread(data.load);
      break;
    case 'initFilter':
      for (var i=0; i<data.filterTable.length; i++) {
        facFilter[data.filterTable[i].locTypeId] = {'icon':data.filterTable[i].icon};
      }
      break;
    case 'filter':
      facFilter = [];
      for (var i=0; i<data.filterTable.length; i++) {
        if (data.filterTable[i].facSelected)
          facFilter[data.filterTable[i].locTypeId] = {'icon':data.filterTable[i].icon};
      }
      break;
    case 'search':
      var searchVintage = ++currVintage;
      var tableSearch = new searcher(searchVintage, data);
      break;
    case 'reset':
      reset();
      self.postMessage({'reset': true});
      break;
    case 'stop':
      self.postMessage({'success' : true});
      self.close(); 
      break;
    default:
      self.postMessage({'success' : false, 'msg' : data.msg});
  };
}, false);

function initThread(msg) 
{
    facilityTable = JSON.parse(msg);
    reset();

    self.postMessage({'success' : true, 
                      'cnt'     : facilityTable.length
                    });     
}   

function reset() 
{
    for (var i=0; i<facilityTable.length; i++) {
        facilityTable[i].visible=false
    }
    currVintage = 0;
}   

function searcher(searchVintage, msg)
{
    var myVintage = searchVintage;
    var facIndex  = -1;
    var msg       = msg;

    var checkLoop = function()
    {
        if (myVintage != currVintage)
            return;

        if (++facIndex == facilityTable.length)
            return;

        inBounds = geoFencer.call(this, msg);

        if (inBounds) {
            var facMatch = 0;
            var bubbleHTML = "";
            for (var i=0; i<facilityTable[facIndex].facilities.length; i++){
                var currFac = facilityTable[facIndex].facilities[i];
                if (facFilter[currFac.locTypeId] != undefined) {
                    if (facMatch != 0) {
                        lineBreak = (facMatch / 3);
                        if (lineBreak == lineBreak.toFixed(0)) {
                            bubbleHTML += "<br />";
                        }
                    }
                    facMatch++;
                    bubbleHTML += imageProlog + facFilter[currFac.locTypeId].icon + imageEpilog;

                }
            }
            if (facMatch == 0) {
                inBounds = false;
            }
        }

        if (inBounds != facilityTable[facIndex].visible) {
            self.postMessage({'match'       : inBounds,
                              'facIndex'    : facIndex,
                              'scopeVintage': msg.scopeVintage,
                              'bubbleHTML'  : bubbleHTML,
                              'success'     : true
                            }); 
            facilityTable[facIndex].visible = inBounds;
        }

        setTimeout(checkLoop,0);
    }

    var circleCheck = function(msg) 
    {
        var diff = Tier3Toolbox.calculateDistance(
                        msg.centerLat,
                        msg.centerLng,
                        facilityTable[facIndex].searchLat,
                        facilityTable[facIndex].searchLng);

        if (msg.radius > diff)
            return true;        

        return false;
    }

    var rectangleCheck = function(msg) 
    {
        if (facilityTable[facIndex].searchLat > msg.SWLat &&
            facilityTable[facIndex].searchLat < msg.NELat &&
            facilityTable[facIndex].searchLng > msg.SWLng &&
            facilityTable[facIndex].searchLng < msg.NELng)
            return true;        

        return false;
    }

    var GEOFENCER = [circleCheck,rectangleCheck];
    var geoFencer = GEOFENCER[msg.checker];

    setTimeout(checkLoop,0);
    return this;
}

The "Toolbox" functions referred to are : -

    function Tier3Toolbox() 
{
    return this;
}

Tier3Toolbox.EARTH_RADIUS = 6378137; /* Equitorial Radius instead of 6371000 */
Tier3Toolbox.toRad = 
    function (num) {
        return num * Math.PI / 180;
    };  
Tier3Toolbox.calculateDistance =
    function(lat1, lon1, lat2, lon2){
        var dLat = this.toRad(lat2 - lat1);
        var dLon = this.toRad(lon2 - lon1);
        var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.toRad(lat1)) * 
                Math.cos(this.toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
        var distance = this.EARTH_RADIUS * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        return distance;
    }

Tier3Toolbox.prototype.callAJAX = 
    function(url, method, callback, serverArgs) 
    {
        var callback = callback;
        var xmlhttp;
        var target = url;
        var args = (serverArgs != undefined) ? serverArgs : "";
        var postArgs = "";
        var callbackArgs = new Array();

        for (i = 4; i < arguments.length; i++) {
            callbackArgs[i - 3] = arguments[i];
        }

        if (window.XMLHttpRequest) {
            xmlhttp = new XMLHttpRequest();
        } else {
            xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
        }

        callbackArgs[0] = xmlhttp;

        if (method.toUpperCase() == "GET") {
            target = target + "?" + args;
        } 

        xmlhttp.onreadystatechange = function () {
            if (xmlhttp.readyState == 4) {
                if (xmlhttp.status == 200) {
                    callback.apply(this, callbackArgs)
                } else {
                    throw new Error("Error making Ajax call to " + target + " Status = " + xmlhttp.status);
                }
            }
        };

        xmlhttp.open(method, url, true);
        if (method.toUpperCase() == "POST") {
            xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            postArgs = args;
        }
        xmlhttp.send(postArgs);
    }

Tier3Toolbox.reportError =      
    function(error) 
    {
        var header  = error.header  || "Error";
        var message = error.message || "";
        var topWindow=window.top.document.open();
        topWindow.write("<!DOCTYPE html><html><body style='height: 100%;'><hr><h1>" + header + "</h1><hr>");
        topWindow.write("<h2>Please contact Server Support for assistance.</h2><br />");
        topWindow.write('<p style="color:red">' + message + "</p></body></html>");
        topWindow.close();
        return;
    }

In you mainline you need to add listeners like: -

    google.maps.event.addDomListener(radarCircle,    'center_changed', reScope);
    google.maps.event.addDomListener(radarCircle,    'radius_changed', reScope);
    google.maps.event.addDomListener(radarRectangle, 'bounds_changed', reScope);


    function createFacilityMarkers(xmlhttp){
        facFinder = new Worker("facfinder.js");
        facFinder.addEventListener('message', workerInit, false);

        facFinder.postMessage({'cmd' : 'init', 'load' : xmlhttp.responseText});
     }

    function reScope() {

    var searchReq = {'cmd':'search', 'scopeVintage':scopeVintage};
    if (radarShape.getCenter) {
        searchReq.checker = 0;
        var currCenter = radarCircle.getCenter();
        searchReq.centerLat = currCenter.lat();
        searchReq.centerLng = currCenter.lng();         
        searchReq.radius = radarCircle.getRadius();
    } else {
        searchReq.checker = 1;
        searchReq.SWLat = radarShape.getBounds().getSouthWest().lat();
        searchReq.SWLng = radarShape.getBounds().getSouthWest().lng();
        searchReq.NELat = radarShape.getBounds().getNorthEast().lat();
        searchReq.NELng = radarShape.getBounds().getNorthEast().lng();
    }

    facFinder.postMessage(searchReq);
}

HTH

Cheers Richard

Zelig answered 2/8, 2018 at 2:56 Comment(1)
Thanks @Zelig I have a similar question I am wondering if you can have a look.Homologize
A
1

Following @SirPeople suggestions here is the complete code that addresses your core problem statement of getting location input from user, update map, and set dynamic radius around it.

enter image description here

here is JS Fiddle link https://jsfiddle.net/innamhunzai/63vcthp7/3/

JS:

var circle;
var map;
function initMap() {
    var centerCoordinates = new google.maps.LatLng(37.6, -95.665);
    map = new google.maps.Map(document.getElementById('map'), {
              center: centerCoordinates,
              zoom: 4
          });
    var card = document.getElementById('pac-card');
    var input = document.getElementById('pac-input');
        
    var infowindowContent = document.getElementById('infowindow-content');
        
    var autocomplete = new google.maps.places.Autocomplete(input);
    var infowindow = new google.maps.InfoWindow();
    infowindow.setContent(infowindowContent);
        
    var marker = new google.maps.Marker({
          map: map
    });
        
     
        circle = new google.maps.Circle({
           map: map,
           strokeColor: "#FF0000",
           strokeOpacity: 0.8,
           strokeWeight: 2,
           fillColor: "#FF0000",
           fillOpacity: 0.35,
        });

    autocomplete.addListener('place_changed', function() {
            document.getElementById("location-error").style.display = 'none';
            infowindow.close();
            marker.setVisible(false);
                var place = autocomplete.getPlace();
                if (!place.geometry) {
                    document.getElementById("location-error").style.display = 'inline-block';
                    document.getElementById("location-error").innerHTML = "Cannot Locate '" + input.value + "' on map";
                    return;
                }
                
            map.fitBounds(place.geometry.viewport);
            marker.setPosition(place.geometry.location);
            circle.setCenter(place.geometry.location);
                marker.setVisible(true);
            circle.setVisible(true);
                    
                infowindowContent.children['place-icon'].src = place.icon;
                infowindowContent.children['place-name'].textContent = place.name;
                infowindowContent.children['place-address'].textContent = input.value;
                infowindow.open(map, marker);
        });
    }
    
    function updateRadius() {
    circle.setRadius(document.getElementById('radius').value * 1609.34);
        map.fitBounds(circle.getBounds());
}

**CSS:**
#map {
    height: 400px;
  }

**HTML**
<html>
    <link href="style.css" rel="stylesheet" type="text/css">
  <body>
    <div class="pac-card" id="pac-card">
      <div>
        <div id="label">
          Location search
        </div>       
      </div>
      <div id="pac-container">
        <input id="pac-input" type="text" placeholder="Enter a location">
        <div id="location-error"></div>
      </div>
      <div>
        <input type="range" id="radius" name="radius" min="0" max="100" onchange="updateRadius()">
      </div>
    </div>
    <div id="map"></div>
    <div id="infowindow-content">
      <img src="" width="16" height="16" id="place-icon">
      <span id="place-name"  class="title"></span><br>
      <span id="place-address"></span>
    </div>
    <script src="https://maps.googleapis.com/maps/api/js?libraries=places&callback=initMap"
        async defer></script>
    </body>
</html>
Addicted answered 26/6, 2021 at 9:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.