如何使用 google 地图 API 制作一个在特定半径范围内搜索项目的表单?

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

我正在制作一个 website,我想在 google 地图上围绕当前位置或某个人工地址画一个圆圈。

例如:用户想要创建一个从当前位置到 30KM 的圆圈,或者用户想要创建一个从某个随机地址到 20KM 的圆圈。

我用来制作搜索半径搜索栏的HTML代码是:

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



问题陈述:

(1) 我想知道我需要进行哪些更改 或需要添加哪些代码 以便搜索项目围绕一个特定的半径。我想,我需要整合代码 Google Maps circle 但我不确定我该怎么做。

(2)website 上点击搜索半径后,以下 options/screen 将出现在底部:

让我们试着给你一些第一步,我不会编写整个应用程序的代码,而是给你一些指导方针来解决你遇到的小问题:

在地图中添加圆圈

嗯,为此你有很多不同的输入选项,但最重要的部分是 addCircle 函数:

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

中心可以来自 click 例如:

// 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);
});

或通过获取某个地址的位置(This is documented as well),或任何方法(拖放圆圈,拖动标记 blablabla)

添加动态半径

好吧,如果我们知道 the radius of the Circle is given in meters,那么就很容易为 addCircle 函数提供正确的半径。例如,20 公里 -> 20 000 米。所以你只需要在调用 addCircle 时能够访问半径(它可以是一个参数,一个全局变量......你的选择)。

绘图部分已经完成,现在让我们在圆圈内搜索。

只获取圆圈内的标记

这里有一个先决条件,要有你地图上的所有标记。您可能有一系列从数据库中获得的地点,或者您可能 get the markers from Google Maps API(例如地点搜索)。

之后你将不得不计算这些标记和你给定的中心之间的距离,并检查距离是否小于你的半径(使用 computeDistanceBetween 很容易),所以你会知道哪些标记对你有效。

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;

剩下的工作应该很简单,place the markers in the map 并根据这些信息做任何您想做的事。

额外内容

作为进一步的帮助,有一对 examples/answers 可能对您有用:

Full Google Map API example,非常简单的分步指南。

Radius search using places,关于如何进行半径搜索的一个很好的答案。

Example of radius search,喜欢的话打开F12调试代码,其实很容易跟上

编辑**:我没有意识到其中 2 个 link 在评论中也指出了。

我建议使用工作线程进行搜索,以通过在后台进行搜索来释放 UI 线程。如果用户移动圆圈或 expands/contracts 它也很有用,因为可以放弃之前的 search/render 匹配标记,

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;
}

提到的"Toolbox"函数是:-

    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;
    }

在您的主线中,您需要添加如下侦听器:-

    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

干杯理查德

这里遵循@SirPeople 的建议是解决您的核心问题陈述的完整代码,即从用户那里获取位置输入、更新地图并在其周围设置动态半径。

这里是JSFiddlelinkhttps://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>