根据GPS坐标更新谷歌地图位置

1 投票
1 回答
2409 浏览
提问于 2025-04-17 09:11

我有一个带GPS芯片的Arduino,正在用Python处理NMEA字符串。我有一个HTML文件,设置了每隔几秒自动刷新一次,这样标记就会更新,但我希望能在不刷新页面的情况下更新位置信息。我知道可以通过拖拽来实现这个功能,像这里演示的那样:http://gmaps-samples-v3.googlecode.com/svn/trunk/draggable-markers/draggable-markers.html,但我需要知道如何替换这个拖拽事件,而是通过Python推送新的坐标。我需要一种方法把我的新坐标信息放到网页里。任何帮助或建议都非常感谢。这个问题唯一相关的类是GoogleMap。我对网页的东西不太熟悉,所以越简单越好。

我现在有的:

Python -> 打开网页并自动刷新
Python -> 在map.html中写入新的坐标
map.html 刷新并显示新的位置

我想要的:

Python -> 新位置 {{ }} 标记移动到新坐标

import  re
import  sys
import  copy
import  time
import  threading
import  Queue
import  serial
import  webbrowser
import  traceback
import  random
import  math
import  turtle
from    pprint         import pprint
from    collections    import OrderedDict

class MockIo(object):

    def __init__(self):
        pass

    def read(self,buff):
        lat = str(random.random())[2:6]
        lon = str(random.random())[2:6]
        return "$GPGGA,172307.000,3913.%s,N,07716.%s,W,2,10,0.8,199.9,M,-33.4,M,3.8,0000*46\r\n" % (lat,lon)

    def write(self,buff):
        pass

class GPSTurtle(object):

    def __init__(self, new_x = 0, new_y = 0):
        self.t = turtle.Turtle()
        self.x_coord = new_x
        self.y_coord = new_y

        self.diff_x  = 0
        self.diff_y  = 0
        self.heading = 0
        self.origin_x = 0
        self.origin_y = 0

    def initialize_origin(self, new_x, new_y):
        self.origin_x = self.origin_x - new_x
        self.origin_y = self.origin_y - new_y

    def __update_pos(self, new_x, new_y):
        new_x       += self.origin_x
        new_y       += self.origin_y
        new_x *= 20
        new_y *= 20
        self.diff_x  = new_x - self.x_coord
        self.diff_y  = new_y - self.y_coord
        if 0 == self.diff_x:
            if self.diff_y > 0:
                self.heading = 90
            elif self.diff_y < 0:
                self.heading = 270
        elif 0 == self.diff_y:
            if self.diff_x > 0:
                self.heading = 0
            elif self.diff_x < 0:
                self.heading = 180
        else:
            self.heading = math.degrees(math.atan(float(self.diff_y)/float(self.diff_x)))
            if self.diff_x < 0:
                self.heading += 180
            elif self.diff_y < 0:
                self.heading += 360

        self.set_pos(new_x, new_y)
        print self.diff_x,self.diff_y,self.heading,self.x_coord,self.y_coord

    def set_pos(self, new_x, new_y):
        self.x_coord = new_x
        self.y_coord = new_y

    def __draw(self):
        self.t.setheading(self.heading)
        self.t.pendown()
        self.t.goto(self.x_coord, self.y_coord)
        self.t.penup()

    def ungps(self, new_x, new_y):
        new_x = int(1000.0 * new_x)
        new_y = int(1000.0 * new_y)
        return (new_x, new_y)

    def update_and_draw(self, new_x, new_y):
        self.__update_pos(new_x, new_y)
        self.__draw()




class GPS(threading.Thread):

    def __init__(self, comport = 'COM15', baud = 4800):
        super(GPS, self).__init__()
        self.GOOD       = True
        self.gpgga_keys =   [
                                'message_id',
                                'utc_time',
                                'lattitude',
                                'n_s_ind',
                                'longitude',
                                'e_w_ind',
                                'pos_fix_ind',
                                'satellites',
                                'hdop',
                                'msl_altitude',
                                'units_1',
                                'geoid_sep',
                                'units_2',
                                # 'age_of_diff_corr', gps does not have this field by default
                                'diff_ref_station_id',
                                'checksum',
                            ]
        self.PSRF103    =   {
                                'name':'$PSRF103',
                                'msg':{'GGA':'00','GLL':'01','GSA':'02','GSV':'03','RMC':'04','VTG':'05'},
                                'mode':{'SetRate':'00','Query':'01'},
                                'rate':{'off':'00','min':'01','max':'255'},
                                'cksumEnable':{'disable':'00','enable':'01'},
                            }
        self.gps_msg_q  = Queue.Queue()
        self.gps_buff   = ""
        try:
            self.gps_com    = serial.Serial(   
                                                comport, 
                                                baud, 
                                                timeout = 1,
                                                parity  = serial.PARITY_NONE,
                                                rtscts  = 0,
                                                xonxoff = 0
                                            )
        except serial.serialutil.SerialException:
            print "Could not open com port, assuming simulation mode and setting"
            print "com object to MockIo"
            self.gps_com = MockIo()

    def enable_all(self):
        m  = self.PSRF103
        for msg in m['msg'].values():
            st = ','.join([m['name'],msg,m['mode']['Query'],m['rate']['on'],m['cksumEnable']['enable']])
            st = self.append_crc(st)
            self.send_msg(st)
        self.gps_com.read(4028)

    def disable_all(self):
        m  = self.PSRF103
        for msg in m['msg'].values():
            st = ','.join([m['name'],msg,m['mode']['Query'],m['rate']['off'],m['cksumEnable']['enable']])
            st = self.append_crc(st)
            self.send_msg(st)
        self.gps_com.read(4028)

    def append_crc(self,st):
        match     = re.compile("\$(.*)")
        crc        = 0

        if match.search(st):
            st = match.search(st).group(1)

        for letter in st:
            crc = crc ^ ord(letter)
        return "$%s*%0.2x\r\n" % (st,crc)

    def run(self):
        self.disable_all()
        while self.GOOD:
            self.send_GPGGA_req()
            time.sleep(2)

    def send_GPGGA_req(self):
        m  = self.PSRF103
        st = ','.join([m['name'],m['msg']['GGA'],m['mode']['Query'],m['rate']['off'],m['cksumEnable']['enable']])
        st = self.append_crc(st)
        self.send_msg(st)

    def parse_msg(self,st):
        '''
            SAMPLE GPGGA MSG
            "$GPGGA,172307.000,3913.7428,N,07716.7474,W,2,10,0.8,199.9,M,-33.4,M,3.8,0000*46\r\n"
        '''
        retVal = (False,None)
        st     = st.rstrip('\r\n')
        parse  = st.split(',')
        if st.startswith('$GPGGA') and len(self.gpgga_keys) == len(parse):
            retVal = (True, OrderedDict(zip(self.gpgga_keys,parse)))
        else:
            pass

        return retVal

    def send_msg(self, st):
        self.gps_com.write(st)
        self.gps_buff = ''.join([self.gps_buff,self.gps_com.read(1024)])
        buffsplit     = re.compile(r'.*?\r\n|.+')
        splt          = buffsplit.findall(self.gps_buff)
        if 0 < len(splt):
            if splt[-1].endswith('\r\n'):
                self.add_list_to_q(splt)
                self.gps_buff = ""
            else:
                self.add_list_to_q(splt[:-1])
                self.gps_buff = splt[-1]

    def add_list_to_q(self,list_):
        for item in list_:
            self.gps_msg_q.put(item,False)

    def get_item_from_q(self, block = True, timeout = 10):
        return self.gps_msg_q.get(block, timeout)

    def convert_lat_lon(self, lat, lon,ns,ew):
        lat = "%f" % (float(lat[:-7]) + (float(lat[-7:])/60.0))
        lon = "%f" % (float(lon[:-7]) + (float(lon[-7:])/60.0))
        if 'S' == ns:
            lat = str(float(lat) * -1.0)
        if 'W' == ew:
            lon = str(float(lon) * -1.0)
        return (lat,lon)




class GoogleMap(object):

    def __init__(self, path = 'map.html'):
        self.path = path
        self.map_html = '''
                            <!DOCTYPE html>
                            <html>
                            <head>
                            <meta http-equiv="refresh" content="5" /> 
                            <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
                            <style type="text/css">
                              html { height: 100% }   body { height: 100%; margin: 0px; padding: 0px }   #map_canvas { height: 100% } </style>
                            <script type="text/javascript"
                                   src="http://maps.google.com/maps/api/js?sensor=true">
                            </script>
                            <script type="text/javascript">
                               function initialize() {
                                   var lat = %s
                                   var lng = %s
                                   var latlng = new google.maps.LatLng(lat,lng);
                                   var myOptions = {
                                       zoom: 13,
                                       center: latlng,
                                       mapTypeId: google.maps.MapTypeId.ROADMAP
                                   };
                                   var map    = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
                                   var marker = new google.maps.Marker({position: latlng, map: map, title: %s });
                               }
                            </script>
                            </head>
                            <body onload="initialize()">
                              <div id="map_canvas" style="width:100%; height:100%"></div>
                            </body>
                            </html>
                        '''

    def write_map(self,lat = '39.229013',long = '-77.445735',marker = '""'):
        new_map_html = self.map_html % ( str(lat), str(long), str(marker).replace('"','') )
        with open(self.path, 'w') as f:
            f.write(new_map_html)

    def launch_browser(self):
        webbrowser.open_new_tab(self.path)



if __name__ == "__main__":
    map = GoogleMap('map.html')
    map.write_map()
    map.launch_browser()

    gps = GPS('COM15',4800)
    gps.start()

    t = GPSTurtle()
    first_update = True

    try:
        while True:
            try:
                st = gps.get_item_from_q(True,2)
                success,gpgga = gps.parse_msg(st)
                if success:
                    lat, lon = gps.convert_lat_lon( gpgga['lattitude'],
                                                    gpgga['longitude'],
                                                    gpgga['n_s_ind'],
                                                    gpgga['e_w_ind'])
                    la,ln = t.ungps(float(lat),float(lon))
                    if first_update:
                        t.initialize_origin(la,ln)
                        first_update = False
                    else:
                        t.update_and_draw(la,ln)

                    map.write_map(lat,lon,'ME!')
                    time.sleep(5)
            except Queue.Empty:
                # pass
                print "Q-Empty"
    except:
        gps.GOOD = False
        gps.join()
        print "\n\nEXITING PROGRAM\n\n"
        traceback.print_exc(file=sys.stdout)

1 个回答

1

与其频繁刷新页面,不如设置一个ajax短轮询。简单来说,就是让你的页面每隔几秒去检查一下某个接口,看看标记的位置。这里有一篇文章讨论这个方法,并链接到一个教程:使用jQuery在网页上获取实时数据

另一种选择是设置长轮询,也就是让页面与服务器保持一个开放的连接,等待更新。这样好处是,一旦坐标有变化,你的界面就会立刻更新。但缺点是,这样会给服务器带来很大的负担,因为它需要一直保持这个连接。而且,长轮询的扩展性比较差。

这篇文章很好地总结了这两种方法:短轮询与长轮询在实时网页应用中的比较

编辑:

这里有一个比较简单的例子,虽然有点粗糙,但应该能给你一个持续更新经纬度的基本框架。提前道歉,因为我没有使用过网络服务器,经验有限,所以我用的是我能想到的最简单的设置——谷歌应用引擎SDK。它自带一个非常容易安装和运行的开发服务器。这里是SDK的链接:http://code.google.com/appengine/downloads.html

main.py:

#!/usr/bin/env python

import os

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util
from google.appengine.ext.webapp import template

def doRender(handler, page, templatevalues=None):
    path = os.path.join(os.path.dirname(__file__), page)
    handler.response.out.write(template.render(path, templatevalues))

class MainHandler(webapp.RequestHandler):
    def get(self):
        doRender(self, 'template/main.html')


class AjaxHandler(webapp.RequestHandler):
    def get(self):
        self.response.out.write('{ "lat": "1", "long": "1"}')

def main():
    application = webapp.WSGIApplication([('/', MainHandler),
                                          ('/data.js', AjaxHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()

app.yaml:

application: ajaxtest
version: 1
runtime: python
api_version: 1

handlers:
- url: /favicon\.ico
  static_files: favicon.ico
  upload: favicon\.ico

- url: .*
  script: main.py

main.html(把这个放在项目根目录下的“template”文件夹里):

<html>
<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"></script>

</head>
<body>
<button>Get JSON data</button>
<div></div>
<script type="text/javascript">
var test = 1;

function update(){
    $.getJSON("/data.js",function(data){

    var items = [];
      $.each(data, function(key, val){
        $("div").append(key + ":" + val + " ");
      });
    });
    }

var t=setInterval("update()",1000);

</script>
</body>
</html>

根据你的需求,修改AjaxHandler以重新查询GPS坐标。同时修改$("div").append(key + ":" + val + " ");,以便将标记的坐标更新到谷歌地图上。

如果这对你没有帮助,或者你在使用twisted时遇到问题,请告诉我。今天我休假,所以应该能花更多时间来帮你。祝你好运!

撰写回答