Reputation: 1469
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
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