Posted in jQuery, ColdFusion | Posted on 01-20-2010 | 2,925 views
Yesterday I blogged about a proof of concept 911 viewer I built using CFMap and jQuery. The first example simply retrieved all of the 911 reports and mapped them at once. The second demo was more complex. This demo actually showed you map data from the beginning of the collection to the most recent report. Watch the video if that doesn't make sense. Let's look at how I built that demo. (Warning: I'm going to jump around a bit code wise but at the end I'll paste the entire template.)
I began with a map centered on my home town:
2</cfmap>
I then created a DIV that would store the current time:
Next - I needed to tell ColdFusion to run a JavaScript function when everything (in this case, the map) was loaded. The last line of my script was:
This ran my init function. It's main purpose was to just set things up and run my main (and repeatable) function to display data.
2 map = ColdFusion.Map.getMapObject("mainMap")
3 doMarkers()
4}
I'm using map there as a global variable. Remember that this is the "real" Google Map API object. When I have that I can do anything Google allows via their API. Let's look at doMarkers now.
2
3 if(prevMarkers.length) {
4 for(var i=0;i<prevMarkers.length;i++) {
5 map.removeOverlay(prevMarkers[i])
6 }
7 prevMarkers = []
8 }
9
10 current++
11 var thisDate = bucketArray[current]["date"]
12 console.log(thisDate + ' has '+bucketArray[current].records.length +' items')
13 $("#clock").text(thisDate)
14 for(var i=0; i<bucketArray[current]["records"].length;i++) {
15 var point = new GLatLng(bucketArray[current].records[i]["latitude"],bucketArray[current].records[i]["longitude"])
16 var icon = getIcon(bucketArray[current].records[i]["type"])
17
18 var iconOb = new GIcon(G_DEFAULT_ICON);
19 if(icon != "") iconOb.image = icon;
20 var marker = new GMarker(point, {icon:iconOb})
21 prevMarkers[prevMarkers.length]=marker
22 map.addOverlay(marker)
23 }
24
25 if(current+1 < bucketArray.length) window.setTimeout("doMarkers()", 1000)
26
27}
Ok, a lot going on here. I begin by seeing if I have previous markers to delete. This will be true on the second iteration and will be true until the "viewer" is done. I'll end up storing my pointers in an array so I simply loop over that and run removeOverlay().
Next I work with a data structure called bucketArray. I'm going to explain how that is built later, but for now, just know it stores a date/time value and a list of 911 data. This is already ordered by time already, so as we go through the array we are going through time. You can see where I update the time div. Then I loop through my data. For each I figure out if I need a custom icon, and if so, I set it up. I then add the marker to the map being sure to store the result in my prevMarkers array. The last line simply calls out to itself to run the next block of data.
My getIcon function just translates the 911 "type" into a custom icon:
2 s = s.toLowerCase()
3 if(s.indexOf("vehicle accident") >= 0) return "icons/car.png";
4 if(s.indexOf("stalled vehicle") >= 0) return "icons/car.png";
5 if(s.indexOf("traffic control") >= 0) return "icons/stop.png";
6 if(s.indexOf("traffic signal") >= 0) return "icons/stop.png";
7 if(s.indexOf("fire") >= 0) return "icons/fire.png";
8 if(s.indexOf("hazard") >= 0) return "icons/hazard.png";
9 return "";
10}
I could update this as more types of emergencies occur. I'm still waiting for "Giant Monster" or "Alien Invasion." For the most part, that's the gist of the JavaScript. So how did I create my data? I began with a query:
2select longitude, latitude, type, incidenttime
3from data
4where
5 (longitude !='' and latitude != '')
6 <!--- fixes one bad row --->
7 and longitude < -88
8 and incidenttime is not null
9 order by incidenttime asc
10</cfquery>
I then figure out the range of my data:
2<cfset earliest = getdata.incidenttime[1]>
3<cfset latest = getdata.incidenttime[getdata.recordcount]>
4<cfset earliest = dateFormat(earliest, "m/d/yy") & " " & timeFormat(earliest, "H") & ":00">
5<cfset latest = dateFormat(latest, "m/d/yy") & " " & timeFormat(latest, "H") & ":00">
Next I initialize my bucketArray:
2<cfloop from="#earliest#" to="#latest#" index="x" step="#createTimeSpan(0,1,0,0)#">
3 <cfset arrayAppend(bucketArray, {date="#dateformat(x)# #timeformat(x)#"})>
4</cfloop>
5<!--- add one to top --->
6<cfset toAppend = dateAdd("h", 1, bucketArray[arrayLen(bucketArray)].date)>
7<cfset arrayAppend(bucketArray, {date="#dateFormat(toAppend)# #timeFormat(toAppend)#"})>
8<!--- i'm pretty sure it is safe to do this --->
9<cfset arrayDeleteAt(bucketArray, 1)>
So what's with the 'add one to top' and 'delete the bottom'? My thinking here was - if my last 911 report was for 5:14PM, I wanted to have this reported as 6PM. Every time value you see represents all the values for the last hour. By the same token, if my first bucket item is 1PM, it really means an item for 1:Something PM. Therefore I can drop that as well.
The final step was to populate the data:
2 <cfset prev = dateAdd("h", -1, b.date)>
3 <cfquery name="getInBucket" dbtype="query">
4 select *
5 from getdata
6 where incidenttime >= <cfqueryparam cfsqltype="cf_sql_timestamp" value="#prev#">
7 and incidenttime < <cfqueryparam cfsqltype="cf_sql_timestamp" value="#b.date#">
8 </cfquery>
9 <cfset b.records = getInBucket>
10</cfloop>
To be honest, I bet I could do this all in one cfquery, probably by simply reformatting the date. Either way - it worked. So how did I get this bucketArray into JavaScript? It was incredibly difficult:
2var #toScript(bucketArray,"bucketArray",false)#;
3</cfoutput>
Yeah, cry over that PHP boys. Anyway, here is the complete CFM file. I'll remind folks that I wrote this very quickly, so please forgive any obvious dumb mistakes.
2select longitude, latitude, type, incidenttime
3from data
4where
5 (longitude !='' and latitude != '')
6 <!--- fixes one bad row --->
7 and longitude < -88
8 and incidenttime is not null
9 order by incidenttime asc
10</cfquery>
11
12
13<!--- generate range of buckets --->
14<cfset earliest = getdata.incidenttime[1]>
15<cfset latest = getdata.incidenttime[getdata.recordcount]>
16<cfset earliest = dateFormat(earliest, "m/d/yy") & " " & timeFormat(earliest, "H") & ":00">
17<cfset latest = dateFormat(latest, "m/d/yy") & " " & timeFormat(latest, "H") & ":00">
18
19<cfset bucketArray = []>
20<cfloop from="#earliest#" to="#latest#" index="x" step="#createTimeSpan(0,1,0,0)#">
21 <cfset arrayAppend(bucketArray, {date="#dateformat(x)# #timeformat(x)#"})>
22</cfloop>
23<!--- add one to top --->
24<cfset toAppend = dateAdd("h", 1, bucketArray[arrayLen(bucketArray)].date)>
25<cfset arrayAppend(bucketArray, {date="#dateFormat(toAppend)# #timeFormat(toAppend)#"})>
26<!--- i'm pretty sure it is safe to do this --->
27<cfset arrayDeleteAt(bucketArray, 1)>
28
29<!---
30<cfloop index="index" from="1" to="#arrayLen(bucketArray)#">
31--->
32<cfloop index="b" array="#bucketArray#">
33 <cfset prev = dateAdd("h", -1, b.date)>
34 <cfquery name="getInBucket" dbtype="query">
35 select *
36 from getdata
37 where incidenttime >= <cfqueryparam cfsqltype="cf_sql_timestamp" value="#prev#">
38 and incidenttime < <cfqueryparam cfsqltype="cf_sql_timestamp" value="#b.date#">
39 </cfquery>
40 <cfset b.records = getInBucket>
41</cfloop>
42<!---
43<cfdump var="#bucketarray#">
44--->
45
46<html>
47
48<head>
49<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
50<script>
51<cfoutput>
52var #toScript(bucketArray,"bucketArray",false)#;
53</cfoutput>
54
55var current = -1;
56var mainHB;
57var prevMarkers = []
58var map;
59
60function getIcon(s) {
61 s = s.toLowerCase()
62 if(s.indexOf("vehicle accident") >= 0) return "icons/car.png";
63 if(s.indexOf("stalled vehicle") >= 0) return "icons/car.png";
64 if(s.indexOf("traffic control") >= 0) return "icons/stop.png";
65 if(s.indexOf("traffic signal") >= 0) return "icons/stop.png";
66 if(s.indexOf("fire") >= 0) return "icons/fire.png";
67 if(s.indexOf("hazard") >= 0) return "icons/hazard.png";
68 return "";
69}
70
71function doMarkers() {
72
73 if(prevMarkers.length) {
74 for(var i=0;i<prevMarkers.length;i++) {
75 map.removeOverlay(prevMarkers[i])
76 }
77 prevMarkers = []
78 }
79
80 current++
81 var thisDate = bucketArray[current]["date"]
82 console.log(thisDate + ' has '+bucketArray[current].records.length +' items')
83 $("#clock").text(thisDate)
84 for(var i=0; i<bucketArray[current]["records"].length;i++) {
85 var point = new GLatLng(bucketArray[current].records[i]["latitude"],bucketArray[current].records[i]["longitude"])
86 var icon = getIcon(bucketArray[current].records[i]["type"])
87
88 var iconOb = new GIcon(G_DEFAULT_ICON);
89 if(icon != "") iconOb.image = icon;
90 var marker = new GMarker(point, {icon:iconOb})
91 prevMarkers[prevMarkers.length]=marker
92 map.addOverlay(marker)
93 }
94
95 if(current+1 < bucketArray.length) window.setTimeout("doMarkers()", 1000)
96
97}
98
99function init() {
100 map = ColdFusion.Map.getMapObject("mainMap")
101 doMarkers()
102}
103</script>
104<style>
105#clock {
106 font-weight:bold;
107 font-size: 40px;
108 font-family: "Courier New", Courier, monospace;
109}
110</style>
111</head>
112
113<body>
114
115<cfmap centeraddress="Lafayette, LA" width="500" height="500" zoomlevel="12" showcentermarker="false" name="mainMap">
116</cfmap>
117
118<div id="clock"></div>
119</body>
120</html>
121
122<cfset ajaxOnLoad("init")>


[Add Comment] [Subscribe to Comments]