如何根据视图边界动态地从Django向谷歌地图提供标记?
我有一个Django数据库模型,长这样:
class Point(models.Model):
date = models.DateTimeField(auto_now_add=True)
long = models.DecimalField(max_digits=10, decimal_places=7)
lat = models.DecimalField(max_digits=10, decimal_places=7)
def as_json(self):
return dict(lat=str(self.lat),lng=str(self.long))
def __unicode__(self):
return "date: %s \nlong: %s \nlat: %s\n" % (str(self.date), str(self.lat), str(self.long))
我的问题:
我想在前端做一个可以和谷歌地图互动的功能,这个功能会发送请求,包含视图的左下角和右上角的坐标。然后服务器会把这个视图分成10*10的小格子,并给我每个小格子里最新的100个点。
请求内容:
var tosend = {
coords: m_map.getBounds(),
zoom: m_map.getZoom()
};
// alert(JSON.stringify(tosend));
$.ajax({
type: "POST",
url: "/points/",
async: "true",
dataType: "json",
data: JSON.stringify(tosend),
success: updatePins
});
服务器看到的内容:
getBounds()返回一个对象,内容是{'pa':{lat : 某个纬度, long: 某个经度}, 'xa'{lat : 某个纬度, long: 某个经度}
举个例子(在Python这边):
{u'coords': {u'pa': {u'k': -173.3125, u'j': -122.6875}, u'xa': {u'k': -42.51152335203773, u'j': 74.60508620624063}}, u'zoom': 2}
我在写代码前的想法:
Django服务器在这个视图中接收POST数据,并处理所有内容。理论上,sendNeeded()这个算法应该返回视图中的所有点,因为我还没有限制查询条件。
我的数据库里有超过30,000个间隔良好的坐标,这绝对不是问题。我觉得问题出在我的算法上,或者我误解了gmaps API通过"getBounds()"返回的内容。
重要提示:
我注意到,当我把视图缩放到美国或其他特定地方时,偶尔会返回大约10,000个点,但这并不稳定,我知道这不对。
服务器视图:
from django.shortcuts import render
from django.http import HttpResponse
from django.template import RequestContext, loader
import json
from models import Point
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def points(request):
return json_flights(request)
def globe(request):
template = loader.get_template('points/globe.html')
context = RequestContext(request)
return HttpResponse(template.render(context))
def json_flights(request):
input = json.loads(request.body)
lb_lat = input['coords']['xa']['k'],
lb_long = input['coords']['pa']['j'],
rt_lat = input['coords']['pa']['k'],
rt_long = input['coords']['xa']['j'],
zoom = input['zoom']
case = 0
#create map_view object for easy passability
map_view = {
'lb_lat' : input['coords']['xa']['k'],
'lb_long' : input['coords']['pa']['j'],
'rt_lat' : input['coords']['pa']['k'],
'rt_long' : input['coords']['xa']['j'],
'zoom' : input['zoom'],
'case' : 0
}
# assign the case to it
if (lb_long < rt_long) and (lb_lat < rt_lat):
map_view['case'] = 0
elif (lb_long < rt_long) and (lb_lat > rt_lat):
map_view['case'] = 1
elif (lb_long > rt_long) and (lb_lat < rt_lat):
map_view['case'] = 2
elif (lb_long > rt_long) and (lb_lat > rt_lat):
map_view['case'] = 3
print "input from client: ", input
pointstosend = []
pointlist = []
#run function to send 100 per square
pointlist = sendOnlyNeeded(map_view)
#get ready to send
json_results = {
'insertdatapayloadhere': "Sample Value",
'coords': pointlist
}
# print "json to send::: ", jsontosend
return HttpResponse(json.dumps(json_results), content_type='application/json')
def sendOnlyNeeded(map_view):
zoom = map_view['zoom']
points_to_return = []
if map_view['case'] == 0:
view_width = map_view['rt_long'] - map_view['lb_long']
view_height = map_view['rt_lat'] - map_view['lb_lat']
elif map_view['case'] == 1:
view_width = map_view['rt_long'] - map_view['lb_long']
view_height = (90 - map_view['lb_lat']) + (map_view['rt_lat'] + 90)
elif map_view['case'] == 2:
view_width = (map_view['rt_long'] + 180) + (180 - map_view['lb_long'])
view_height = map_view['rt_lat'] - map_view['lb_lat']
elif map_view['case'] == 3:
view_width = map_view['rt_long'] - map_view['lb_long']
view_height = map_view['rt_lat'] - map_view['lb_lat']
print 'view_width', view_width , 'view_height', view_height
# constant for tweaking
number_per_unit = 100
# set the grid dimensions
griddimensions = [10, 10]
#find the height and width of each rectangle within the view
unit_height = view_height / griddimensions[1]
unit_width = view_width / griddimensions[0]
#iterate by viewable gridunit and filter the num_per_gu markers to send.
# starting at lb_lat, chop up and push markers from grid until you get to rt_lat
# start>------->
# >------------>
# >--------->end
long_cursor = map_view['lb_long']
lat_cursor = map_view['lb_lat']
widthcount = 0;
heightcount = 0
print "view height::::::", view_height
while heightcount < view_height:
if lat_cursor > 90:
lat_cursor = -90
while widthcount < view_width:
unitbounds = {
'lb_lat': lat_cursor,
'rt_lat': lat_cursor + unit_height,
'lb_long': long_cursor ,
'rt_long': long_cursor + unit_width
}
print "unitbounds: ", unitbounds
querySetResult = getMarkersinBox(unitbounds, number_per_unit)
for cords in querySetResult:
points_to_return.append([float(cords.long), float(cords.lat)])
#points_to_return.append(preparedata(getMarkersinBox(unitbounds, number_per_unit)))
#check if you went off the map. notice we let it overflow. this way, we're guaranteed not to miss any points.
long_cursor += unit_width
if long_cursor > 180:
long_cursor = -180
#counter to stop
widthcount += unit_width
print "widthcount::::", widthcount
lat_cursor += unit_height
heightcount += unit_height
print "heightcount:::", heightcount
return points_to_return
def getMarkersinBox(unitbounds, number_per_unit):
from django.db.models import Q
numbtosend = number_per_unit
lb_lat = unitbounds['lb_lat']
lb_long = unitbounds['lb_long']
rt_lat = unitbounds['rt_lat']
rt_long = unitbounds['rt_long']
if (lb_long < rt_long) and (lb_lat < rt_lat):
queryresult = Point.objects.filter(long__gte=lb_long, long__lte=rt_long, lat__gte=lb_lat, lat__lte=rt_lat).order_by('id')
elif (lb_long < rt_long) and (lb_lat > rt_lat):
queryresult = Point.objects.filter(Q(long__gte=lb_long), Q(long__lte=rt_long), Q(lat__gte=lb_lat) | Q(lat__lte=rt_lat)).order_by('id')
elif (lb_long > rt_long) and (lb_lat < rt_lat):
queryresult = Point.objects.filter(Q(long__gte=lb_long) | Q(long__lte=rt_long), Q(lat__gte=lb_lat), Q(lat__lte=rt_lat)).order_by('id')
elif (lb_long > rt_long) and (lb_lat > rt_lat):
queryresult = Point.objects.filter(Q(long__gte=lb_long) | Q(long__lte=rt_long), Q(lat__gte=lb_lat) | Q(lat__lte=rt_lat)).order_by('id')
return queryresult
(为了测试,csrf不检查)。
为了保险起见,我的所有地图代码:
var m_infoBox = null;
var m_map;
var markers = [];
var image = {
url: '/static/pictures/map_globe_pin.png',
size: new google.maps.Size(18, 17),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(8, 8)
};
var sampd;
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(32.000000, 32.000000),
zoom: 2,
minZoom: 2,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
m_map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions);
google.maps.event.addListener(m_map, 'idle', queryServerForMarkers);
google.maps.event.addListenerOnce(m_map, 'idle', function() {
fixPanHandles();
});
google.maps.event.addListenerOnce(m_map, 'idle', function() {
setTimeout(function() {
fixPanHandles();
}, 500);
});
google.maps.event.addListener(m_map, 'bounds_changed', function() {
checkIfMarkersStillVisible();
});
m_map.getMinimumResolution = function() {
return 12;
};
}
google.maps.event.addDomListener(window, 'load', initialize);
function checkIfMarkersStillVisible() {
for (var i = 0; i < markers.length; i++) {
if (!m_map.getBounds().contains(markers[i].getPosition())) {
markers[i].setMap(null);
} else {
markers[i].setMap(m_map);
}
}
}
function InfoBox(opts, uid, mid, content) {
google.maps.OverlayView.call(this);
this.latlng_ = opts.latlng;
this.map_ = opts.map;
this.offsetVertical_ = -76;
this.offsetHorizontal_ = -105;
this.height_ = 58;
this.uid_ = uid;
this.mid_ = mid;
this.content_ = content;
var me = this;
this.boundsChangedListener_ =
google.maps.event.addListener(this.map_, "bounds_changed", function() {});
this.setMap(this.map_);
return this;
}
function queryServerForMarkers() {
//clean up first
var tosend = {
coords: m_map.getBounds(),
zoom: m_map.getZoom()
};
// alert(JSON.stringify(tosend));
$.ajax({
type: "POST",
url: "/points/",
async: "true",
dataType: "json",
data: JSON.stringify(tosend),
success: updatePins
});
}
function updatePins(data) {
sampd = data;
//tells you center:
console.table("Bounds of view: " + m_map.getBounds() + "\ngot new pins: " + data.coords.length + JSON.stringify(data.coords));
for (var i = 0; i < data.length; i++) {
if ($.inArray(data[i], markers) != -1) {
continue;
}
var coords = new google.maps.LatLng(data.coords[i][0], data.coords[i][1]);
setMarker(m_map, coords, 0, 1223, 1122);
//debug for coords
console.log({
'lat': parseFloat(data.coords[i][0]),
'lng': parseFloat(data.coords[i][1])
});
}
}
function setMarker(map, markerLatLng, markerzIndex, uid, mid) {
var marker = new google.maps.Marker({
position: markerLatLng,
map: map,
icon: image,
// animation: google.maps.Animation.DROP,
zIndex: markerzIndex
});
google.maps.event.addListener(marker, 'mouseover', function() {
if (m_infoBox) {
if (m_infoBox.mid_ == mid) {
return;
}
m_infoBox.setMap(null);
m_infoBox = null;
}
content = loadContent(uid);
m_infoBox = new InfoBox({
latlng: marker.getPosition(),
map: m_map
}, uid, mid, content);
});
markers.push(marker);
}
function loadContent(uid) {
var response = '';
$.ajax({
type: "GET",
url: "/service/globe_float_content/?uid=" + uid,
async: true,
success: function(text) {
response = text;
}
});
return response;
}
InfoBox.prototype = new google.maps.OverlayView();
InfoBox.prototype.remove = function() {
if (this.div_) {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
InfoBox.prototype.draw = function() {
// Creates the element if it doesn't exist already.
this.createElement();
if (!this.div_) return;
// Calculate the DIV coordinates of two opposite corners of our bounds to
// get the size and position of our Bar
var pixPosition = this.getProjection().fromLatLngToDivPixel(this.latlng_);
if (!pixPosition) return;
this.div_.style.display = 'block';
// Assign the correct width for the popup pane to fully display all details in the popup
var divWidth = $('#float_avatar').width() + $('#float_name').width() + 32;
$('#float_tip_down').css("left", ((divWidth / 2) - 20) + "px");
this.div_.style.width = divWidth + "px";
this.div_.style.left = (pixPosition.x - (divWidth / 2)) + "px";
this.div_.style.height = this.height_ + "px";
this.div_.style.top = (pixPosition.y + this.offsetVertical_) + "px";
};
function fixPanHandles() {
//fix bug with IE and gmaps pan hotspots.
$('#map-canvas div[title^="Pan"]').css("opacity", 0);
}
function setAllMap(map) {
for (var i = 0; i < markers.length; i++) {
markers[i].setMap(map);
}
}
1 个回答
0
我想确认一下geocodezip说的内容。在玩这个API的时候,我发现对于边界角落“pa”,纬度是'j',经度是'k'。但是对于边界角落“xa”,纬度是'k',经度是'j'。我不能确定这些映射是否一致,所以使用这个API可能会更好。
如果这还不够帮助你,或者你还是卡住了,那我希望你能提供更多关于你的代码到底想做什么的细节。在你发的代码里,我需要花点时间才能搞清楚我应该关注哪个部分。