Initial HTTP support with planes animated using google map.

This commit is contained in:
antirez 2013-01-13 01:39:29 +01:00
parent 4203b9caf6
commit a583615b66
3 changed files with 371 additions and 91 deletions

View file

@ -16,7 +16,10 @@ The main features are:
* Interactive mode where aircrafts currently detected are shown * Interactive mode where aircrafts currently detected are shown
as a list refreshing as more data arrives. as a list refreshing as more data arrives.
* CPR coordinates decoding and track calculation from velocity. * CPR coordinates decoding and track calculation from velocity.
* TCP server streaming and recceiving raw data to/from connected clients (using --net). * TCP server streaming and recceiving raw data to/from connected clients
(using --net).
* Embedded HTTP server that displays the currently detected aircrafts on
Google Map.
Installation Installation
--- ---
@ -39,6 +42,11 @@ To run the program in interactive mode:
./dump1090 --interactive ./dump1090 --interactive
To run the program in interactive mode, with networking support, and connect
with your browser to http://localhost:8080 to see live traffic:
./dump1090 --interactive --net
In iteractive mode it is possible to have a less information dense but more In iteractive mode it is possible to have a less information dense but more
"arcade style" output, where the screen is refreshed every second displaying "arcade style" output, where the screen is refreshed every second displaying
all the recently seen aircrafts with some additional information such as all the recently seen aircrafts with some additional information such as
@ -136,7 +144,11 @@ normal traffic from RTL devices or from file when --ifile is used.
It is possible to use Dump1090 just as an hub using --ifile with /dev/zero It is possible to use Dump1090 just as an hub using --ifile with /dev/zero
as argument as in the following example: as argument as in the following example:
./dump1090 --ifile /dev/zero --net --interactive ./dump1090 --net-only
Or alternatively to see what's happening on the screen:
./dump1090 --net-only --interactive
Then you can feed it from different data sources from the internet. Then you can feed it from different data sources from the internet.

View file

@ -40,6 +40,7 @@
#include <signal.h> #include <signal.h>
#include <fcntl.h> #include <fcntl.h>
#include <ctype.h> #include <ctype.h>
#include <sys/stat.h>
#include "rtl-sdr.h" #include "rtl-sdr.h"
#include "anet.h" #include "anet.h"
@ -81,7 +82,8 @@
#define MODES_NET_MAX_FD 1024 #define MODES_NET_MAX_FD 1024
#define MODES_NET_OUTPUT_RAW_PORT 30002 #define MODES_NET_OUTPUT_RAW_PORT 30002
#define MODES_NET_INPUT_RAW_PORT 30001 #define MODES_NET_INPUT_RAW_PORT 30001
#define MODES_CLIENT_BUF_SIZE 256 #define MODES_NET_HTTP_PORT 8080
#define MODES_CLIENT_BUF_SIZE 1024
#define MODES_NOTUSED(V) ((void) V) #define MODES_NOTUSED(V) ((void) V)
@ -142,6 +144,7 @@ struct {
int maxfd; /* Greatest fd currently active. */ int maxfd; /* Greatest fd currently active. */
int ros; /* Raw output listening socket. */ int ros; /* Raw output listening socket. */
int ris; /* Raw input listening socket. */ int ris; /* Raw input listening socket. */
int https; /* HTTP listening socket. */
/* Configuration */ /* Configuration */
char *filename; /* Input form file, --ifile option. */ char *filename; /* Input form file, --ifile option. */
@ -150,8 +153,10 @@ struct {
int raw; /* Raw output format. */ int raw; /* Raw output format. */
int debug; /* Debugging mode. */ int debug; /* Debugging mode. */
int net; /* Enable networking. */ int net; /* Enable networking. */
int net_only; /* Enable just networking. */
int net_output_raw_port; /* Raw output TCP port. */ int net_output_raw_port; /* Raw output TCP port. */
int net_input_raw_port; /* Raw input TCP port. */ int net_input_raw_port; /* Raw input TCP port. */
int net_http_port; /* HTTP port. */
int interactive; /* Interactive mode */ int interactive; /* Interactive mode */
int interactive_rows; /* Interactive mode: max number of rows. */ int interactive_rows; /* Interactive mode: max number of rows. */
int interactive_ttl; /* Interactive mode: TTL before deletion. */ int interactive_ttl; /* Interactive mode: TTL before deletion. */
@ -169,6 +174,7 @@ struct {
long long stat_goodcrc; long long stat_goodcrc;
long long stat_badcrc; long long stat_badcrc;
long long stat_fixed; long long stat_fixed;
long long stat_http_requests;
} Modes; } Modes;
/* The struct we use to store information about a decoded message. */ /* The struct we use to store information about a decoded message. */
@ -244,8 +250,10 @@ void modesInitConfig(void) {
Modes.check_crc = 1; Modes.check_crc = 1;
Modes.raw = 0; Modes.raw = 0;
Modes.net = 0; Modes.net = 0;
Modes.net_only = 0;
Modes.net_output_raw_port = MODES_NET_OUTPUT_RAW_PORT; Modes.net_output_raw_port = MODES_NET_OUTPUT_RAW_PORT;
Modes.net_input_raw_port = MODES_NET_INPUT_RAW_PORT; Modes.net_input_raw_port = MODES_NET_INPUT_RAW_PORT;
Modes.net_http_port = MODES_NET_HTTP_PORT;
Modes.onlyaddr = 0; Modes.onlyaddr = 0;
Modes.debug = 0; Modes.debug = 0;
Modes.interactive = 0; Modes.interactive = 0;
@ -292,6 +300,7 @@ void modesInit(void) {
Modes.stat_goodcrc = 0; Modes.stat_goodcrc = 0;
Modes.stat_badcrc = 0; Modes.stat_badcrc = 0;
Modes.stat_fixed = 0; Modes.stat_fixed = 0;
Modes.stat_http_requests = 0;
Modes.exit = 0; Modes.exit = 0;
} }
@ -1257,9 +1266,13 @@ void detectModeS(uint16_t *m, uint32_t mlen) {
* further processing and visualization. */ * further processing and visualization. */
void useModesMessage(struct modesMessage *mm) { void useModesMessage(struct modesMessage *mm) {
if (!Modes.stats && (Modes.check_crc == 0 || mm->crcok)) { if (!Modes.stats && (Modes.check_crc == 0 || mm->crcok)) {
if (Modes.interactive) { /* Track aircrafts in interactive mode or if the HTTP
* interface is enabled. */
if (Modes.interactive || Modes.stat_http_requests > 0) {
interactiveReceiveData(mm); interactiveReceiveData(mm);
} else { }
/* In non-interactive way, display messages on standard output. */
if (!Modes.interactive) {
displayModesMessage(mm); displayModesMessage(mm);
if (!Modes.raw && !Modes.onlyaddr) printf("\n"); if (!Modes.raw && !Modes.onlyaddr) printf("\n");
} }
@ -1596,27 +1609,31 @@ void snipMode(int level) {
/* Networking "stack" initialization. */ /* Networking "stack" initialization. */
void modesInitNet(void) { void modesInitNet(void) {
struct {
char *descr;
int *socket;
int port;
} services[3] = {
{"Raw TCP output", &Modes.ros, Modes.net_output_raw_port},
{"Raw TCP input", &Modes.ris, Modes.net_input_raw_port},
{"HTTP server", &Modes.https, Modes.net_http_port}
};
int j;
memset(Modes.clients,0,sizeof(Modes.clients)); memset(Modes.clients,0,sizeof(Modes.clients));
Modes.maxfd = -1; Modes.maxfd = -1;
/* Raw output port */ for (j = 0; j < 3; j++) {
Modes.ros = anetTcpServer(Modes.aneterr, Modes.net_output_raw_port, NULL); int s = anetTcpServer(Modes.aneterr, services[j].port, NULL);
if (Modes.ros == -1) { if (s == -1) {
fprintf(stderr, "Error opening raw TCP output port %d: %s\n", fprintf(stderr, "Error opening the listening port %d (%s): %s\n",
Modes.net_output_raw_port, Modes.aneterr); services[j].port, services[j].descr, strerror(errno));
exit(1); exit(1);
} }
anetNonBlock(Modes.aneterr, s);
/* Raw input port */ *services[j].socket = s;
Modes.ris = anetTcpServer(Modes.aneterr, Modes.net_input_raw_port, NULL);
if (Modes.ris == -1) {
fprintf(stderr, "Error opening raw TCP input port %d: %s\n",
Modes.net_input_raw_port, Modes.aneterr);
exit(1);
} }
anetNonBlock(Modes.aneterr, Modes.ros);
anetNonBlock(Modes.aneterr, Modes.ris);
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
} }
@ -1627,10 +1644,11 @@ void modesAcceptClients(void) {
int fd, port; int fd, port;
unsigned int j; unsigned int j;
struct client *c; struct client *c;
int services[2]; int services[3];
services[0] = Modes.ros; services[0] = Modes.ros;
services[1] = Modes.ris; services[1] = Modes.ris;
services[2] = Modes.https;
for (j = 0; j < sizeof(services)/sizeof(int); j++) { for (j = 0; j < sizeof(services)/sizeof(int); j++) {
fd = anetTcpAccept(Modes.aneterr, services[j], NULL, &port); fd = anetTcpAccept(Modes.aneterr, services[j], NULL, &port);
@ -1712,16 +1730,30 @@ int hexDigitVal(int c) {
/* This function decodes a string representing a Mode S message in /* This function decodes a string representing a Mode S message in
* raw hex format like: *8D4B969699155600E87406F5B69F; * raw hex format like: *8D4B969699155600E87406F5B69F;
* The string is supposed to be at the start of the client buffer
* and null-terminated.
* *
* The message is passed to the higher level layers, so it feeds * The message is passed to the higher level layers, so it feeds
* the selected screen output, the network output and so forth. * the selected screen output, the network output and so forth.
* *
* If the message looks invalid is silently discarded. */ * If the message looks invalid is silently discarded. */
void decodeHexMessage(char *hex) { void decodeHexMessage(struct client *c) {
char *hex = c->buf;
int l = strlen(hex), j; int l = strlen(hex), j;
unsigned char msg[MODES_LONG_MSG_BYTES]; unsigned char msg[MODES_LONG_MSG_BYTES];
struct modesMessage mm; struct modesMessage mm;
/* Remove spaces on the left and on the right. */
while(l && isspace(hex[l-1])) {
hex[l-1] = '\0';
l--;
}
while(isspace(*hex)) {
hex++;
l--;
}
/* Turn the message into binary. */
if (l < 2 || hex[0] != '*' || hex[l-1] != ';') return; if (l < 2 || hex[0] != '*' || hex[l-1] != ';') return;
hex++; l-=2; /* Skip * and ; */ hex++; l-=2; /* Skip * and ; */
if (l > MODES_LONG_MSG_BYTES*2) return; /* Too long message... broken. */ if (l > MODES_LONG_MSG_BYTES*2) return; /* Too long message... broken. */
@ -1736,52 +1768,164 @@ void decodeHexMessage(char *hex) {
useModesMessage(&mm); useModesMessage(&mm);
} }
/* This function polls all the clients using read() in order to receive new /* Return a description of planes in json. */
char *aircraftsToJson(int *len) {
struct aircraft *a = Modes.aircrafts;
int buflen = 1024; /* The initial buffer is incremented as needed. */
char *buf = malloc(buflen), *p = buf;
int l;
l = snprintf(p,buflen,"[\n");
p += l; buflen -= l;
while(a) {
int altitude = a->altitude, speed = a->speed;
/* Convert units to metric if --metric was specified. */
if (Modes.metric) {
altitude /= 3.2828;
speed *= 1.852;
}
if (a->lat != 0 && a->lon != 0) {
l = snprintf(p,buflen,
"{\"hex\":\"%s\", \"lat\":%f, \"lon\":%f, \"track\":%d},\n",
a->hexaddr, a->lat, a->lon, a->track);
p += l; buflen -= l;
/* Resize if needed. */
if (buflen < 256) {
int used = p-buf;
buflen += 1024; /* Our increment. */
buf = realloc(buf,used+buflen);
p = buf+used;
}
}
a = a->next;
}
/* Remove the final comma if any, and closes the json array. */
if (*(p-2) == ',') {
*(p-2) = '\n';
p--;
buflen++;
}
l = snprintf(p,buflen,"]\n");
p += l; buflen -= l;
*len = p-buf;
return buf;
}
#define MODES_CONTENT_TYPE_HTML "text/html;charset=utf-8"
#define MODES_CONTENT_TYPE_JSON "application/json;charset=utf-8"
/* Get an HTTP request header and write the response to the client.
* Again here we assume that the socket buffer is enough without doing
* any kind of userspace buffering. */
void handleHTTPRequest(struct client *c) {
char hdr[512];
int clen, hdrlen;
int keepalive;
char *p, *url, *content;
char *ctype;
/* printf("HTTP request: %s\n", c->buf); */
/* Minimally parse the request. */
keepalive = strstr(c->buf, "keep-alive") != NULL;
p = strchr(c->buf,' ');
if (!p) return; /* There should be the method and a space... */
url = ++p; /* Now this should point to the requested URL. */
p = strchr(p, ' ');
if (!p) return; /* There should be a space before HTTP/... */
*p = '\0';
/* printf("URL: %s\n", url); */
/* Select the content to send, we have just two so far:
* "/" -> Our google map application.
* "/data.json" -> Our ajax request to update planes. */
if (strstr(url, "/data.json")) {
content = aircraftsToJson(&clen);
ctype = MODES_CONTENT_TYPE_JSON;
} else {
struct stat sbuf;
int fd = -1;
if (stat("gmap.html",&sbuf) != -1 &&
(fd = open("gmap.html",O_RDONLY)) != -1)
{
content = malloc(sbuf.st_size);
read(fd,content,sbuf.st_size);
} else {
char buf[128];
clen = snprintf(buf,sizeof(buf),"Error opening HTML file: %s",
strerror(errno));
content = strdup(buf);
}
if (fd != -1) close(fd);
ctype = MODES_CONTENT_TYPE_HTML;
}
/* Create the header and send the reply. */
hdrlen = snprintf(hdr, sizeof(hdr),
"HTTP/1.1 200 OK\r\n"
"Server: Dump1090\r\n"
"Content-Type: %s\r\n"
"Connection: %s\r\n"
"Content-Length: %d\r\n"
"\r\n",
ctype,
keepalive ? "keep-alive" : "close",
clen);
write(c->fd, hdr, hdrlen);
/* Send the actual content. */
write(c->fd, content, clen);
free(content);
Modes.stat_http_requests++;
}
/* This function polls the clients using read() in order to receive new
* messages from the net. * messages from the net.
* *
* Every full message received is decoded and passed to the higher layers. */ * The message is supposed to be separated by the next message by the
void modesReceiveRawInput(void) { * separator 'sep', that is a null-terminated C string.
int j; *
struct client *c; * Every full message received is decoded and passed to the higher layers
* calling the function 'handler'. */
for (j = 0; j <= Modes.maxfd; j++) { void modesReadFromClient(struct client *c, char *sep,
c = Modes.clients[j]; void(*handler)(struct client *))
if (c && c->service == Modes.ris) { {
while(1) { while(1) {
int left = sizeof(c->buf) - c->buflen; int left = sizeof(c->buf) - c->buflen;
int nread = read(j, c->buf+c->buflen, left); int nread = read(c->fd, c->buf+c->buflen, left);
int decoded = 0; int fullmsg = 0;
int oldpos = c->buflen;
int i; int i;
char *p;
if (nread < 0) { if (nread < 0) {
if (nread == 0 || errno != EAGAIN) { if (nread == 0 || errno != EAGAIN) {
/* Error, or end of file. */ /* Error, or end of file. */
modesFreeClient(j); modesFreeClient(c->fd);
} }
break; /* Serve next client. */ break; /* Serve next client. */
} }
c->buflen += nread; c->buflen += nread;
/* If there is a complete message there must be a newline /* If there is a complete message there must be the separator 'sep'
* in the buffer. The iteration starts from 'oldpos' as * in the buffer, note that we full-scan the buffer at every read
* we need to check only the chars we read in this interaction * for simplicity. */
* as we are sure there is no newline in the pre-existing if ((p = strstr(c->buf, sep)) != NULL) {
* buffer. */ i = p - c->buf; /* Turn it as an index inside the buffer. */
for (i = oldpos; i < c->buflen; i++) { c->buf[i] = '\0'; /* Te handler expects null terminated strings. */
if (c->buf[i] == '\n') { handler(c); /* Call the function to process the message. */
c->buf[i] = '\0';
if (i && c->buf[i-1] == '\r') c->buf[i-1] = '\0';
decodeHexMessage(c->buf);
/* Move what's left at the start of the buffer. */ /* Move what's left at the start of the buffer. */
i++; i += strlen(sep); /* The separator is part of the previous msg. */
memmove(c->buf,c->buf+i,c->buflen-i); memmove(c->buf,c->buf+i,c->buflen-i);
c->buflen -= i; c->buflen -= i;
/* Maybe there are more messages inside the buffer. /* Maybe there are more messages inside the buffer.
* Start looping from the start again. */ * Start looping from the start again. */
i = -1; i = -1;
decoded = 1; fullmsg = 1;
}
} }
/* If our buffer is full discard it, this is some badly /* If our buffer is full discard it, this is some badly
* formatted shit. */ * formatted shit. */
@ -1792,9 +1936,22 @@ void modesReceiveRawInput(void) {
} }
/* If no message was decoded process the next client, otherwise /* If no message was decoded process the next client, otherwise
* read more data from the same client. */ * read more data from the same client. */
if (!decoded) break; if (!fullmsg) break;
}
} }
}
/* Read data from clients. This function actually delegates a lower-level
* function that depends on the kind of service (raw, http, ...). */
void modesReadFromClients(void) {
int j;
struct client *c;
for (j = 0; j <= Modes.maxfd; j++) {
if ((c = Modes.clients[j]) == NULL) continue;
if (c->service == Modes.ris)
modesReadFromClient(c,"\n",decodeHexMessage);
else if (c->service == Modes.https)
modesReadFromClient(c,"\r\n\r\n",handleHTTPRequest);
} }
} }
@ -1812,8 +1969,10 @@ void showHelp(void) {
"--interactive-ttl <sec> Remove from list if idle for <sec> (default: 60).\n" "--interactive-ttl <sec> Remove from list if idle for <sec> (default: 60).\n"
"--raw Show only messages hex values.\n" "--raw Show only messages hex values.\n"
"--net Enable networking.\n" "--net Enable networking.\n"
"--net-only Enable just networking, no RTL device or file used.\n"
"--net-ro-port <port> TCP listening port for raw output (default: 30002).\n" "--net-ro-port <port> TCP listening port for raw output (default: 30002).\n"
"--net-ri-port <port> TCP listening port for raw input (default: 30001).\n" "--net-ri-port <port> TCP listening port for raw input (default: 30001).\n"
"--net-http-port <port> HTTP server port (default: 8080).\n"
"--no-fix Disable single-bits error correction using CRC.\n" "--no-fix Disable single-bits error correction using CRC.\n"
"--no-crc-check Disable messages with broken CRC (discouraged).\n" "--no-crc-check Disable messages with broken CRC (discouraged).\n"
"--stats With --ifile print stats at exit. No other output.\n" "--stats With --ifile print stats at exit. No other output.\n"
@ -1825,6 +1984,26 @@ void showHelp(void) {
); );
} }
/* This function is called a few times every second by main in order to
* perform tasks we need to do continuously, like accepting new clients
* from the net, refreshing the screen in interactive mode, and so forth. */
void backgroundTasks(void) {
if (Modes.net) {
modesAcceptClients();
modesReadFromClients();
}
/* Refresh screen when in interactive mode. */
if (Modes.interactive &&
(mstime() - Modes.interactive_last_update) >
MODES_INTERACTIVE_REFRESH_TIME)
{
interactiveRemoveStaleAircrafts();
interactiveShowData();
Modes.interactive_last_update = mstime();
}
}
int main(int argc, char **argv) { int main(int argc, char **argv) {
int j; int j;
@ -1853,10 +2032,15 @@ int main(int argc, char **argv) {
Modes.raw = 1; Modes.raw = 1;
} else if (!strcmp(argv[j],"--net")) { } else if (!strcmp(argv[j],"--net")) {
Modes.net = 1; Modes.net = 1;
} else if (!strcmp(argv[j],"--net-only")) {
Modes.net = 1;
Modes.net_only = 1;
} else if (!strcmp(argv[j],"--net-ro-port") && more) { } else if (!strcmp(argv[j],"--net-ro-port") && more) {
Modes.net_output_raw_port = atoi(argv[++j]); Modes.net_output_raw_port = atoi(argv[++j]);
} else if (!strcmp(argv[j],"--net-ri-port") && more) { } else if (!strcmp(argv[j],"--net-ri-port") && more) {
Modes.net_input_raw_port = atoi(argv[++j]); Modes.net_input_raw_port = atoi(argv[++j]);
} else if (!strcmp(argv[j],"--net-http-port") && more) {
Modes.net_http_port = atoi(argv[++j]);
} else if (!strcmp(argv[j],"--onlyaddr")) { } else if (!strcmp(argv[j],"--onlyaddr")) {
Modes.onlyaddr = 1; Modes.onlyaddr = 1;
} else if (!strcmp(argv[j],"--metric")) { } else if (!strcmp(argv[j],"--metric")) {
@ -1888,7 +2072,9 @@ int main(int argc, char **argv) {
/* Initialization */ /* Initialization */
modesInit(); modesInit();
if (Modes.filename == NULL) { if (Modes.net_only) {
fprintf(stderr,"Net-only mode, no RTL device or file open.\n");
} else if (Modes.filename == NULL) {
modesInitRTLSDR(); modesInitRTLSDR();
} else { } else {
if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') { if (Modes.filename[0] == '-' && Modes.filename[1] == '\0') {
@ -1900,6 +2086,13 @@ int main(int argc, char **argv) {
} }
if (Modes.net) modesInitNet(); if (Modes.net) modesInitNet();
/* If the user specifies --net-only, just run in order to serve network
* clients without reading data from the RTL device. */
while (Modes.net_only) {
backgroundTasks();
usleep(100000);
}
/* Create the thread that will read the data from the device. */ /* Create the thread that will read the data from the device. */
pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL); pthread_create(&Modes.reader_thread, NULL, readerThreadEntryPoint, NULL);
@ -1922,20 +2115,7 @@ int main(int argc, char **argv) {
* slow processors). */ * slow processors). */
pthread_mutex_unlock(&Modes.data_mutex); pthread_mutex_unlock(&Modes.data_mutex);
detectModeS(Modes.magnitude, Modes.data_len/2); detectModeS(Modes.magnitude, Modes.data_len/2);
if (Modes.net) { backgroundTasks();
modesAcceptClients();
modesReceiveRawInput();
}
/* Refresh screen when in interactive mode. */
if (Modes.interactive &&
(mstime() - Modes.interactive_last_update) >
MODES_INTERACTIVE_REFRESH_TIME)
{
interactiveRemoveStaleAircrafts();
interactiveShowData();
Modes.interactive_last_update = mstime();
}
pthread_mutex_lock(&Modes.data_mutex); pthread_mutex_lock(&Modes.data_mutex);
if (Modes.exit) break; if (Modes.exit) break;
} }

88
gmap.html Normal file
View file

@ -0,0 +1,88 @@
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0; padding: 0 }
#map_canvas { height: 100% }
</style>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js">
</script>
<script type="text/javascript"
src="https://maps.googleapis.com/maps/api/js?sensor=true">
</script>
<script type="text/javascript">
Map=null;
CenterLat=50.0;
CenterLon=9.0;
Planes={};
function getIconForPlane(plane) {
return {
strokeWeight: 2,
path: google.maps.SymbolPath.FORWARD_CLOSED_ARROW,
scale: 5,
fillColor: 'yellow',
fillOpacity: 0.8,
rotation: plane.track
};
}
function fetchData() {
$.getJSON('/data.json', function(data) {
var stillhere = {}
for (var j=0; j < data.length; j++) {
var plane = data[j];
stillhere[plane.hex] = true;
if (Planes[plane.hex]) {
var myplane = Planes[plane.hex];
var marker = myplane.marker;
var icon = marker.getIcon();
var newpos = new google.maps.LatLng(plane.lat, plane.lon);
marker.setPosition(newpos);
marker.setIcon(getIconForPlane(plane));
} else {
var marker = new google.maps.Marker({
position: new google.maps.LatLng(plane.lat, plane.lon),
map: Map,
title: plane.hex,
icon: getIconForPlane(plane)
});
plane.marker = marker;
Planes[plane.hex] = plane;
}
}
/* Remove idle planes. */
for (var p in Planes) {
if (!stillhere[p]) {
Planes[p].marker.setMap(null);
delete Planes[p];
}
}
});
}
function initialize() {
var mapOptions = {
center: new google.maps.LatLng(CenterLat, CenterLon),
zoom: 8,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
Map = new google.maps.Map(document.getElementById("map_canvas"), mapOptions);
/* Setup our timer to poll from the server. */
window.setInterval(function() {
fetchData();
}, 1000);
}
</script>
</head>
<body onload="initialize()">
<div id="map_canvas" style="width:100%; height:100%"></div>
</body>
</html>