pyInTheSky
pyInTheSky

Reputation: 1469

Update google map position based on gps coords

I have an Arduino w/ GPS chip, processing NMEA strings w/ python. I have a HTML file that I open and have auto refresh every x-seconds s.t. the marker updates, but I'd really like to be able to update the position information without doing a refresh. I know it can be done w/ drag and drop as demoed here: http://gmaps-samples-v3.googlecode.com/svn/trunk/draggable-markers/draggable-markers.html but I need to know how to replace that drag event and instead interface w/ python pushing out new coordinates. I need a way of getting my new coordinate information into the web page. Any help/suggestions would be greatly appreciated. The only class really applicable to this problem is the GoogleMap one. I'm not familiar w/ much web stuff, so the simpler the better.

what i have:

python -> opens webpage w/ autoreload python -> writs over map.html plugging in new coords map.html refreshes and picks up and displays new position

what i want:

python -> new position {{ }} marker moves to new coords

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)

Upvotes: 1

Views: 2503

Answers (1)

Sologoub
Sologoub

Reputation: 5352

Instead of doing refreshes, you could setup ajax short polling - essentially have your page check with an end point that returns the position of the marker every few seconds. Here's a post talking about it and linking to a tutorial: Real-time data on webpage with jQuery

Another alternative would be to setup a long poll - have the page keep an open connection with your server waiting for an update. The big up side is that your UI will get updated as soon as there is a change in coordinates. The big downside is that this puts a sizable load on your server, requiring it to keep the open connection. Scaling long polling is harder.

This post summarizes both options well: Short-polling vs Long-polling for real time web applications?

EDIT:

Here's a fairly crude example, but it should give you a simple framework to update the lat long continuously. Apologize in advance, since my experience is limited without using a webserver, I used the simplest one to setup that I can think of - google app engine SDK. It comes with a dev server that is super easy to install and run. Here's the link to the 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 (put this in folder "template" under root folder of the project):

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

For your purposes, modify the AjaxHandler to re-query the GPS coordinates. Also modify the $("div").append(key + ":" + val + " "); to send an update to google maps for the coordinates of the marker.

Let me know if this doesn't help or if you have no luck with twisted. I'm off work today, so should be able to dedicate much more time. Good luck!

Upvotes: 1

Related Questions