如何根据视图边界动态地从Django向谷歌地图提供标记?

0 投票
1 回答
945 浏览
提问于 2025-04-18 11:53

我有一个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可能会更好。

如果这还不够帮助你,或者你还是卡住了,那我希望你能提供更多关于你的代码到底想做什么的细节。在你发的代码里,我需要花点时间才能搞清楚我应该关注哪个部分。

撰写回答