我一直在编写一个小应用程序,其中有很多来自stackoverflow的帮助。基本前提很简单,我在网络上看到了这种功能:我试图在可搜索/可搜索的谷歌地图上绘制一个位置列表。位置存储在后端,控制器将这些位置提供给视图。涉及AJAX因为我不想重新加载整个页面。以下是场景:a)用户通过zipcode =>搜索位置如果设置半径内有任何标记,则地图加载新位置,搜索获取发送到服务器并映射加载任何标记,地图设置默认缩放级别; b)用户平移/缩放=>只要用户离开了地图,地图就会停留,使用视口边界框的搜索会发送到服务器并映射结果。该地图将在初始加载时默认为西雅图,它首先尝试的是对用户进行地理定位...
使用gmaps4ails wiki,主要是这个问题答案的修改版本:Google Maps for Rails - update markers with AJAX我已经非常接近了。实际上,这很有效。这是它的样子:
def search
if params[:lat]
@ll = [params[:lat].to_f, params[:lng].to_f]
@sightings = Sighting.within(5, origin: @ll).order('created_at DESC')
@remap = true
elsif search_params = params[:zipcode]
geocode = Geokit::Geocoders::GoogleGeocoder.geocode(search_params)
@ll = [geocode.lat, geocode.lng]
@sightings = Sighting.within(5, origin: @ll).order('created_at DESC')
@remap = true
elsif params[:bounds]
boundarray = params[:bounds].split(',')
bounds = [[boundarray[0].to_f, boundarray[1].to_f], [boundarray[2].to_f, boundarray[3].to_f]]
@ll = [params[:center].split(',')[0].to_f, params[:center].split(',')[1].to_f]
@sightings = Sighting.in_bounds(bounds, origin: @ll).order('created_at DESC')
@remap = false
else
search_params = '98101'
geocode = Geokit::Geocoders::GoogleGeocoder.geocode(search_params)
@ll = [geocode.lat, geocode.lng]
@sightings = Sighting.within(5, origin: @ll).order('created_at DESC')
@remap = true
end
@hash = Gmaps4rails.build_markers(@sightings) do |sighting, marker|
marker.lat sighting.latitude
marker.lng sighting.longitude
marker.name sighting.title
marker.infowindow view_context.link_to("sighting", sighting)
end
respond_to do |format|
format.html
format.js
end
end
= form_tag search_sightings_path, method: "get", id: "zipform", role: "form", remote: true do
= text_field_tag :zipcode, params[:zipcode], size: 5, maxlength: 5, placeholder: "zipcode", id: "zipsearch"
= button_tag "Search", name: "button"
%input{type: "button", value: "Current Location", onclick: "getUserLocation()"}
#locationData
.sightings_map_container
.sightings_map_canvas#sightings_map_canvas
#sightings_container
- content_for :javascript do
%script{src: "//maps.google.com/maps/api/js?v=3.13&sensor=false&libraries=geometry", type: "text/javascript"}
%script{src: "//google-maps-utility-library-v3.googlecode.com/svn/tags/markerclustererplus/2.0.14/src/markerclusterer_packed.js", type: "text/javascript"}
:javascript
function getUserLocation() {
//check if the geolocation object is supported, if so get position
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setLocation);
}
else {
document.getElementById("locationData").innerHTML = "Sorry - your browser doesn't support geolocation!";
}
}
function setLocation(position) {
//build text string including co-ordinate data passed in parameter
var displayText = "Latitude: " + position.coords.latitude + ", Longitude: " + position.coords.longitude;
//display the string for demonstration
document.getElementById("locationData").innerHTML = displayText;
//submit the lat/lng coordinates of current location
$.get('/sightings/search.js',{lat: position.coords.latitude, lng: position.coords.longitude});
}
// build maps via Gmaps4rails
handler = Gmaps.build('Google');
handler.buildMap({
provider: {
},
internal: {
id: 'sightings_map_canvas'
}
},
function() {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(setLocation);
}
var json_array = #{raw @hash.to_json};
var latlng = #{raw @ll};
resetMarkers(handler, json_array);
resetMap(handler, latlng);
// listen for pan/zoom and submit new coordinates
(function gmaps4rails_callback() {
google.maps.event.addListener(handler.getMap(), 'idle', function() {
var bounds = handler.getMap().getBounds().toUrlValue();
var center = handler.getMap().getCenter().toUrlValue();
$.get('/sightings/search.js',{bounds: bounds, center: center, old_hash: #{raw @hash.to_json}});
})
})();
});
(function() {
var json_array = <%= raw @hash.to_json %>;
if (<%= @remap %>) {
var latlng = <%= raw @ll %>;
resetMarkers(handler, json_array);
resetMap(handler, latlng);
}
else {
resetMarkers(handler, json_array);
}
})();
(function() {
function createSidebarLi(json) {
return ("<li><a>" + json.name + "</a></li>");
};
function bindLiToMarker($li, marker) {
$li.on('click', function() {
handler.getMap().setZoom(18);
marker.setMap(handler.getMap()); //because clusterer removes map property from marker
google.maps.event.trigger(marker.getServiceObject(), 'click');
})
};
function createSidebar(json_array) {
_.each(json_array, function(json) {
var $li = $( createSidebarLi(json) );
$li.appendTo('#sightings_container');
bindLiToMarker($li, json.marker);
});
};
function clearSidebar() {
$('#sightings_container').empty();
};
function clearZipcode() {
$('#zipform')[0].reset();
};
/* __markers will hold a reference to all markers currently shown
on the map, as GMaps4Rails won't do it for you.
This won't pollute the global window object because we're nested
in a "self-executed" anonymous function */
var __markers;
function resetMarkers(handler, json_array) {
handler.removeMarkers(__markers);
clearSidebar();
clearZipcode();
if (json_array.length > 0) {
__markers = handler.addMarkers(json_array);
_.each(json_array, function(json, index){
json.marker = __markers[index];
});
createSidebar(json_array);
}
};
function resetMap(handler, latlng) {
handler.bounds.extendWith(__markers);
handler.fitMapToBounds();
handler.getMap().setZoom(12);
handler.map.centerOn({
lat: latlng[0],
lng: latlng[1]
});
}
// "Publish" our method on window. You should probably have your own namespace
window.resetMarkers = resetMarkers;
window.resetMap = resetMap;
})();
这就是问题所在,这与这个特定的例子有关,因为我似乎误解了javascript(我对它的新变化)的变量是如何工作的。当用户平移和缩放,但搜索结果相同时,我宁愿不调用&#34; resetMarkers&#34;功能,但宁愿只留下地图。该地图目前将始终重置漫游者/侧边栏等,这会在屏幕上产生一点点标记。
我已尝试了几种不同的版本,但不起作用。在map.js中:
var __markers;
var __oldmarkers;
function resetMarkers(handler, json_array) {
if(!(_.isEqual(__oldmarkers, __markers))) {
handler.removeMarkers(__markers);
clearSidebar();
clearZipcode();
if (json_array.length > 0) {
__markers = handler.addMarkers(json_array);
_.each(json_array, function(json, index){
json.marker = __markers[index];
});
createSidebar(json_array);
}
__oldmarkers = __markers.slice(0);
}
};
由于__markers似乎在页面的生命周期中保持其值(我们在设置新标记之前使用它来删除旧标记),我想我可以简单地创建另一个变量来检查它。然而,即使我认为它应该是真的,它总是错误的。
我尝试的另一件事是重新提交旧哈希作为每个搜索请求的参数,然后设置一个标志,但这看起来很复杂,并且字符串/哈希/数组操作让我感到困惑,我放弃了。我真的不认为这是最好的方法,但也许我应该这样做?
或者,是否有一些我完全错过的东西而应该做的呢?
答案 0 :(得分:2)
您的问题在于比较两个标记列表以决定是否应该更新。
问题是,尽管_.isEqual(__oldmarkers, __markers)
确实进行了深入的比较,但是列表中的Marker实例中可能会发生更改,即使是相同的点(id,时间戳,......)也会发生变化。
或许这只是因为在开始时,__markers
和__oldMarkers
都是null
,因此相等,这意味着你永远不会进入if
区块。
无论如何,我认为这里的深度比较可能会变得太昂贵。我要做的是比较容易比较的东西,比如每组标记的平面坐标列表。
这样的事情:
var __markers, __coordinates = [];
function resetMarkers(handler, json_array)
{
var coordinates = _.map(json_array, function(marker) {
return String(marker.lat) + ',' + String(marker.lng);
});
if(_.isEqual(__coordinates.sort(), coordinates.sort()))
{
handler.removeMarkers(__markers);
clearSidebar();
clearZipcode();
if (json_array.length > 0)
{
__markers = handler.addMarkers(json_array);
_.each(json_array, function(json, index){
json.marker = __markers[index];
});
createSidebar(json_array);
}
__coordinates = coordinates;
}
};
此处__coordinates
和coordinates
只是String的平面数组,应该快速比较并为您提供预期的结果。
在命令中使用_.isEqual
进行比较时,两个数组都会事先进行排序。
注意:旧代码使用_.difference但不正确(请参阅评论中的讨论)
(请注意,我正在使用_.difference
,可能比_.isEqual
更昂贵,但奖励是独立于返回的标记顺序。)
编辑:哦,当然你现在可以停止在搜索查询参数中发送“oldHash”;)