Compare View
Commits (2)
Diff
Showing 7 changed files Inline Diff
public/scripts/grade.js
View file @
77ade48
File was created | 1 | var unirest = require('unirest'); | ||
2 | ||||
3 | function getGrade(long, lat){ | |||
4 | unirest.get("https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=37.782551&lon=-122.445368") | |||
5 | .header("X-Mashape-Key", "gj4g6D3zxKmshys7yyf9KNbuywOxp1ueazFjsn6n2fK4sccIGA") | |||
6 | .header("Accept", "application/json") | |||
7 | .end(function (result) { | |||
8 | res.render('pages/tempcrime', result); | |||
9 | console.log(result.status, result.headers, result.body); | |||
10 | }); | |||
11 | } | |||
var unirest = require('unirest'); | 1 | |||
2 | ||||
function getGrade(long, lat){ | 3 | |||
unirest.get("https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=37.782551&lon=-122.445368") | 4 | |||
.header("X-Mashape-Key", "gj4g6D3zxKmshys7yyf9KNbuywOxp1ueazFjsn6n2fK4sccIGA") | 5 | |||
.header("Accept", "application/json") | 6 | |||
.end(function (result) { | 7 | |||
res.render('pages/tempcrime', result); | 8 | |||
console.log(result.status, result.headers, result.body); | 9 | |||
}); | 10 |
public/scripts/routeboxer.js
View file @
77ade48
File was created | 1 | /** | ||
2 | * @name RouteBoxer | |||
3 | * @version 1.0 | |||
4 | * @copyright (c) 2010 Google Inc. | |||
5 | * @author Thor Mitchell | |||
6 | * | |||
7 | * @fileoverview The RouteBoxer class takes a path, such as the Polyline for a | |||
8 | * route generated by a Directions request, and generates a set of LatLngBounds | |||
9 | * objects that are guaranteed to contain every point within a given distance | |||
10 | * of that route. These LatLngBounds objects can then be used to generate | |||
11 | * requests to spatial search services that support bounds filtering (such as | |||
12 | * the Google Maps Data API) in order to implement search along a route. | |||
13 | * <br/><br/> | |||
14 | * RouteBoxer overlays a grid of the specified size on the route, identifies | |||
15 | * every grid cell that the route passes through, and generates a set of bounds | |||
16 | * that cover all of these cells, and their nearest neighbours. Consequently | |||
17 | * the bounds returned will extend up to ~3x the specified distance from the | |||
18 | * route in places. | |||
19 | */ | |||
20 | ||||
21 | /* | |||
22 | * Licensed under the Apache License, Version 2.0 (the "License"); | |||
23 | * you may not use this file except in compliance with the License. | |||
24 | * You may obtain a copy of the License at | |||
25 | * | |||
26 | * http://www.apache.org/licenses/LICENSE-2.0 | |||
27 | * | |||
28 | * Unless required by applicable law or agreed to in writing, software | |||
29 | * distributed under the License is distributed on an "AS IS" BASIS, | |||
30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
31 | * See the License for the specific language governing permissions and | |||
32 | * limitations under the License. | |||
33 | */ | |||
34 | ||||
35 | /** | |||
36 | * Creates a new RouteBoxer | |||
37 | * | |||
38 | * @constructor | |||
39 | */ | |||
40 | function RouteBoxer() { | |||
41 | this.R = 6371; // earth's mean radius in km | |||
42 | this.initGoogle(); | |||
43 | } | |||
44 | ||||
45 | /** | |||
46 | * Generates boxes for a given route and distance | |||
47 | * | |||
48 | * @param {google.maps.LatLng[] | google.maps.Polyline} path The path along | |||
49 | * which to create boxes. The path object can be either an Array of | |||
50 | * google.maps.LatLng objects or a Maps API v2 or Maps API v3 | |||
51 | * google.maps.Polyline object. | |||
52 | * @param {Number} range The distance in kms around the route that the generated | |||
53 | * boxes must cover. | |||
54 | * @return {google.maps.LatLngBounds[]} An array of boxes that covers the whole | |||
55 | * path. | |||
56 | */ | |||
57 | RouteBoxer.prototype.box = function (path, range) { | |||
58 | // Two dimensional array representing the cells in the grid overlaid on the path | |||
59 | this.grid_ = null; | |||
60 | ||||
61 | // Array that holds the latitude coordinate of each vertical grid line | |||
62 | this.latGrid_ = []; | |||
63 | ||||
64 | // Array that holds the longitude coordinate of each horizontal grid line | |||
65 | this.lngGrid_ = []; | |||
66 | ||||
67 | // Array of bounds that cover the whole route formed by merging cells that | |||
68 | // the route intersects first horizontally, and then vertically | |||
69 | this.boxesX_ = []; | |||
70 | ||||
71 | // Array of bounds that cover the whole route formed by merging cells that | |||
72 | // the route intersects first vertically, and then horizontally | |||
73 | this.boxesY_ = []; | |||
74 | ||||
75 | // The array of LatLngs representing the vertices of the path | |||
76 | var vertices = null; | |||
77 | ||||
78 | // If necessary convert the path into an array of LatLng objects | |||
79 | if (path instanceof Array) { | |||
80 | // already an arry of LatLngs (eg. v3 overview_path) | |||
81 | vertices = path; | |||
82 | } else if (path instanceof google.maps.Polyline) { | |||
83 | if (path.getPath) { | |||
84 | // v3 Maps API Polyline object | |||
85 | vertices = new Array(path.getPath().getLength()); | |||
86 | for (var i = 0; i < vertices.length; i++) { | |||
87 | vertices[i] = path.getPath().getAt(i); | |||
88 | } | |||
89 | } else { | |||
90 | // v2 Maps API Polyline object | |||
91 | vertices = new Array(path.getVertexCount()); | |||
92 | for (var j = 0; j < vertices.length; j++) { | |||
93 | vertices[j] = path.getVertex(j); | |||
94 | } | |||
95 | } | |||
96 | } | |||
97 | ||||
98 | // Build the grid that is overlaid on the route | |||
99 | this.buildGrid_(vertices, range); | |||
100 | ||||
101 | // Identify the grid cells that the route intersects | |||
102 | this.findIntersectingCells_(vertices); | |||
103 | ||||
104 | // Merge adjacent intersected grid cells (and their neighbours) into two sets | |||
105 | // of bounds, both of which cover them completely | |||
106 | this.mergeIntersectingCells_(); | |||
107 | ||||
108 | // Return the set of merged bounds that has the fewest elements | |||
109 | return (this.boxesX_.length <= this.boxesY_.length ? | |||
110 | this.boxesX_ : | |||
111 | this.boxesY_); | |||
112 | }; | |||
113 | ||||
114 | /** | |||
115 | * Generates boxes for a given route and distance | |||
116 | * | |||
117 | * @param {LatLng[]} vertices The vertices of the path over which to lay the grid | |||
118 | * @param {Number} range The spacing of the grid cells. | |||
119 | */ | |||
120 | RouteBoxer.prototype.buildGrid_ = function (vertices, range) { | |||
121 | ||||
122 | // Create a LatLngBounds object that contains the whole path | |||
123 | var routeBounds = new google.maps.LatLngBounds(); | |||
124 | for (var i = 0; i < vertices.length; i++) { | |||
125 | routeBounds.extend(vertices[i]); | |||
126 | } | |||
127 | ||||
128 | // Find the center of the bounding box of the path | |||
129 | var routeBoundsCenter = routeBounds.getCenter(); | |||
130 | ||||
131 | // Starting from the center define grid lines outwards vertically until they | |||
132 | // extend beyond the edge of the bounding box by more than one cell | |||
133 | this.latGrid_.push(routeBoundsCenter.lat()); | |||
134 | ||||
135 | // Add lines from the center out to the north | |||
136 | this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range).lat()); | |||
137 | for (i = 2; this.latGrid_[i - 2] < routeBounds.getNorthEast().lat(); i++) { | |||
138 | this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range * i).lat()); | |||
139 | } | |||
140 | ||||
141 | // Add lines from the center out to the south | |||
142 | for (i = 1; this.latGrid_[1] > routeBounds.getSouthWest().lat(); i++) { | |||
143 | this.latGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(180, range * i).lat()); | |||
144 | } | |||
145 | ||||
146 | // Starting from the center define grid lines outwards horizontally until they | |||
147 | // extend beyond the edge of the bounding box by more than one cell | |||
148 | this.lngGrid_.push(routeBoundsCenter.lng()); | |||
149 | ||||
150 | // Add lines from the center out to the east | |||
151 | this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range).lng()); | |||
152 | for (i = 2; this.lngGrid_[i - 2] < routeBounds.getNorthEast().lng(); i++) { | |||
153 | this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range * i).lng()); | |||
154 | } | |||
155 | ||||
156 | // Add lines from the center out to the west | |||
157 | for (i = 1; this.lngGrid_[1] > routeBounds.getSouthWest().lng(); i++) { | |||
158 | this.lngGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(270, range * i).lng()); | |||
159 | } | |||
160 | ||||
161 | // Create a two dimensional array representing this grid | |||
162 | this.grid_ = new Array(this.lngGrid_.length); | |||
163 | for (i = 0; i < this.grid_.length; i++) { | |||
164 | this.grid_[i] = new Array(this.latGrid_.length); | |||
165 | } | |||
166 | }; | |||
167 | ||||
168 | /** | |||
169 | * Find all of the cells in the overlaid grid that the path intersects | |||
170 | * | |||
171 | * @param {LatLng[]} vertices The vertices of the path | |||
172 | */ | |||
173 | RouteBoxer.prototype.findIntersectingCells_ = function (vertices) { | |||
174 | // Find the cell where the path begins | |||
175 | var hintXY = this.getCellCoords_(vertices[0]); | |||
176 | ||||
177 | // Mark that cell and it's neighbours for inclusion in the boxes | |||
178 | this.markCell_(hintXY); | |||
179 | ||||
180 | // Work through each vertex on the path identifying which grid cell it is in | |||
181 | for (var i = 1; i < vertices.length; i++) { | |||
182 | // Use the known cell of the previous vertex to help find the cell of this vertex | |||
183 | var gridXY = this.getGridCoordsFromHint_(vertices[i], vertices[i - 1], hintXY); | |||
184 | ||||
185 | if (gridXY[0] === hintXY[0] && gridXY[1] === hintXY[1]) { | |||
186 | // This vertex is in the same cell as the previous vertex | |||
187 | // The cell will already have been marked for inclusion in the boxes | |||
188 | continue; | |||
189 | ||||
190 | } else if ((Math.abs(hintXY[0] - gridXY[0]) === 1 && hintXY[1] === gridXY[1]) || | |||
191 | (hintXY[0] === gridXY[0] && Math.abs(hintXY[1] - gridXY[1]) === 1)) { | |||
192 | // This vertex is in a cell that shares an edge with the previous cell | |||
193 | // Mark this cell and it's neighbours for inclusion in the boxes | |||
194 | this.markCell_(gridXY); | |||
195 | ||||
196 | } else { | |||
197 | // This vertex is in a cell that does not share an edge with the previous | |||
198 | // cell. This means that the path passes through other cells between | |||
199 | // this vertex and the previous vertex, and we must determine which cells | |||
200 | // it passes through | |||
201 | this.getGridIntersects_(vertices[i - 1], vertices[i], hintXY, gridXY); | |||
202 | } | |||
203 | ||||
204 | // Use this cell to find and compare with the next one | |||
205 | hintXY = gridXY; | |||
206 | } | |||
207 | }; | |||
208 | ||||
209 | /** | |||
210 | * Find the cell a path vertex is in by brute force iteration over the grid | |||
211 | * | |||
212 | * @param {LatLng[]} latlng The latlng of the vertex | |||
213 | * @return {Number[][]} The cell coordinates of this vertex in the grid | |||
214 | */ | |||
215 | RouteBoxer.prototype.getCellCoords_ = function (latlng) { | |||
216 | for (var x = 0; this.lngGrid_[x] < latlng.lng(); x++) {} | |||
217 | for (var y = 0; this.latGrid_[y] < latlng.lat(); y++) {} | |||
218 | return ([x - 1, y - 1]); | |||
219 | }; | |||
220 | ||||
221 | /** | |||
222 | * Find the cell a path vertex is in based on the known location of a nearby | |||
223 | * vertex. This saves searching the whole grid when working through vertices | |||
224 | * on the polyline that are likely to be in close proximity to each other. | |||
225 | * | |||
226 | * @param {LatLng[]} latlng The latlng of the vertex to locate in the grid | |||
227 | * @param {LatLng[]} hintlatlng The latlng of the vertex with a known location | |||
228 | * @param {Number[]} hint The cell containing the vertex with a known location | |||
229 | * @return {Number[]} The cell coordinates of the vertex to locate in the grid | |||
230 | */ | |||
231 | RouteBoxer.prototype.getGridCoordsFromHint_ = function (latlng, hintlatlng, hint) { | |||
232 | var x, y; | |||
233 | if (latlng.lng() > hintlatlng.lng()) { | |||
234 | for (x = hint[0]; this.lngGrid_[x + 1] < latlng.lng(); x++) {} | |||
235 | } else { | |||
236 | for (x = hint[0]; this.lngGrid_[x] > latlng.lng(); x--) {} | |||
237 | } | |||
238 | ||||
239 | if (latlng.lat() > hintlatlng.lat()) { | |||
240 | for (y = hint[1]; this.latGrid_[y + 1] < latlng.lat(); y++) {} | |||
241 | } else { | |||
242 | for (y = hint[1]; this.latGrid_[y] > latlng.lat(); y--) {} | |||
243 | } | |||
244 | ||||
245 | return ([x, y]); | |||
246 | }; | |||
247 | ||||
248 | ||||
249 | /** | |||
250 | * Identify the grid squares that a path segment between two vertices | |||
251 | * intersects with by: | |||
252 | * 1. Finding the bearing between the start and end of the segment | |||
253 | * 2. Using the delta between the lat of the start and the lat of each | |||
254 | * latGrid boundary to find the distance to each latGrid boundary | |||
255 | * 3. Finding the lng of the intersection of the line with each latGrid | |||
256 | * boundary using the distance to the intersection and bearing of the line | |||
257 | * 4. Determining the x-coord on the grid of the point of intersection | |||
258 | * 5. Filling in all squares between the x-coord of the previous intersection | |||
259 | * (or start) and the current one (or end) at the current y coordinate, | |||
260 | * which is known for the grid line being intersected | |||
261 | * | |||
262 | * @param {LatLng} start The latlng of the vertex at the start of the segment | |||
263 | * @param {LatLng} end The latlng of the vertex at the end of the segment | |||
264 | * @param {Number[]} startXY The cell containing the start vertex | |||
265 | * @param {Number[]} endXY The cell containing the vend vertex | |||
266 | */ | |||
267 | RouteBoxer.prototype.getGridIntersects_ = function (start, end, startXY, endXY) { | |||
268 | var edgePoint, edgeXY, i; | |||
269 | var brng = start.rhumbBearingTo(end); // Step 1. | |||
270 | ||||
271 | var hint = start; | |||
272 | var hintXY = startXY; | |||
273 | ||||
274 | // Handle a line segment that travels south first | |||
275 | if (end.lat() > start.lat()) { | |||
276 | // Iterate over the east to west grid lines between the start and end cells | |||
277 | for (i = startXY[1] + 1; i <= endXY[1]; i++) { | |||
278 | // Find the latlng of the point where the path segment intersects with | |||
279 | // this grid line (Step 2 & 3) | |||
280 | edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); | |||
281 | ||||
282 | // Find the cell containing this intersect point (Step 4) | |||
283 | edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); | |||
284 | ||||
285 | // Mark every cell the path has crossed between this grid and the start, | |||
286 | // or the previous east to west grid line it crossed (Step 5) | |||
287 | this.fillInGridSquares_(hintXY[0], edgeXY[0], i - 1); | |||
288 | ||||
289 | // Use the point where it crossed this grid line as the reference for the | |||
290 | // next iteration | |||
291 | hint = edgePoint; | |||
292 | hintXY = edgeXY; | |||
293 | } | |||
294 | ||||
295 | // Mark every cell the path has crossed between the last east to west grid | |||
296 | // line it crossed and the end (Step 5) | |||
297 | this.fillInGridSquares_(hintXY[0], endXY[0], i - 1); | |||
298 | ||||
299 | } else { | |||
300 | // Iterate over the east to west grid lines between the start and end cells | |||
301 | for (i = startXY[1]; i > endXY[1]; i--) { | |||
302 | // Find the latlng of the point where the path segment intersects with | |||
303 | // this grid line (Step 2 & 3) | |||
304 | edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); | |||
305 | ||||
306 | // Find the cell containing this intersect point (Step 4) | |||
307 | edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); | |||
308 | ||||
309 | // Mark every cell the path has crossed between this grid and the start, | |||
310 | // or the previous east to west grid line it crossed (Step 5) | |||
311 | this.fillInGridSquares_(hintXY[0], edgeXY[0], i); | |||
312 | ||||
313 | // Use the point where it crossed this grid line as the reference for the | |||
314 | // next iteration | |||
315 | hint = edgePoint; | |||
316 | hintXY = edgeXY; | |||
317 | } | |||
318 | ||||
319 | // Mark every cell the path has crossed between the last east to west grid | |||
320 | // line it crossed and the end (Step 5) | |||
321 | this.fillInGridSquares_(hintXY[0], endXY[0], i); | |||
322 | ||||
323 | } | |||
324 | }; | |||
325 | ||||
326 | /** | |||
327 | * Find the latlng at which a path segment intersects with a given | |||
328 | * line of latitude | |||
329 | * | |||
330 | * @param {LatLng} start The vertex at the start of the path segment | |||
331 | * @param {Number} brng The bearing of the line from start to end | |||
332 | * @param {Number} gridLineLat The latitude of the grid line being intersected | |||
333 | * @return {LatLng} The latlng of the point where the path segment intersects | |||
334 | * the grid line | |||
335 | */ | |||
336 | RouteBoxer.prototype.getGridIntersect_ = function (start, brng, gridLineLat) { | |||
337 | var d = this.R * ((gridLineLat.toRad() - start.lat().toRad()) / Math.cos(brng.toRad())); | |||
338 | return start.rhumbDestinationPoint(brng, d); | |||
339 | }; | |||
340 | ||||
341 | /** | |||
342 | * Mark all cells in a given row of the grid that lie between two columns | |||
343 | * for inclusion in the boxes | |||
344 | * | |||
345 | * @param {Number} startx The first column to include | |||
346 | * @param {Number} endx The last column to include | |||
347 | * @param {Number} y The row of the cells to include | |||
348 | */ | |||
349 | RouteBoxer.prototype.fillInGridSquares_ = function (startx, endx, y) { | |||
350 | var x; | |||
351 | if (startx < endx) { | |||
352 | for (x = startx; x <= endx; x++) { | |||
353 | this.markCell_([x, y]); | |||
354 | } | |||
355 | } else { | |||
356 | for (x = startx; x >= endx; x--) { | |||
357 | this.markCell_([x, y]); | |||
358 | } | |||
359 | } | |||
360 | }; | |||
361 | ||||
362 | /** | |||
363 | * Mark a cell and the 8 immediate neighbours for inclusion in the boxes | |||
364 | * | |||
365 | * @param {Number[]} square The cell to mark | |||
366 | */ | |||
367 | RouteBoxer.prototype.markCell_ = function (cell) { | |||
368 | var x = cell[0]; | |||
369 | var y = cell[1]; | |||
370 | this.grid_[x - 1][y - 1] = 1; | |||
371 | this.grid_[x][y - 1] = 1; | |||
372 | this.grid_[x + 1][y - 1] = 1; | |||
373 | this.grid_[x - 1][y] = 1; | |||
374 | this.grid_[x][y] = 1; | |||
375 | this.grid_[x + 1][y] = 1; | |||
376 | this.grid_[x - 1][y + 1] = 1; | |||
377 | this.grid_[x][y + 1] = 1; | |||
378 | this.grid_[x + 1][y + 1] = 1; | |||
379 | }; | |||
380 | ||||
381 | /** | |||
382 | * Create two sets of bounding boxes, both of which cover all of the cells that | |||
383 | * have been marked for inclusion. | |||
384 | * | |||
385 | * The first set is created by combining adjacent cells in the same column into | |||
386 | * a set of vertical rectangular boxes, and then combining boxes of the same | |||
387 | * height that are adjacent horizontally. | |||
388 | * | |||
389 | * The second set is created by combining adjacent cells in the same row into | |||
390 | * a set of horizontal rectangular boxes, and then combining boxes of the same | |||
391 | * width that are adjacent vertically. | |||
392 | * | |||
393 | */ | |||
394 | RouteBoxer.prototype.mergeIntersectingCells_ = function () { | |||
395 | var x, y, box; | |||
396 | ||||
397 | // The box we are currently expanding with new cells | |||
398 | var currentBox = null; | |||
399 | ||||
400 | // Traverse the grid a row at a time | |||
401 | for (y = 0; y < this.grid_[0].length; y++) { | |||
402 | for (x = 0; x < this.grid_.length; x++) { | |||
403 | ||||
404 | if (this.grid_[x][y]) { | |||
405 | // This cell is marked for inclusion. If the previous cell in this | |||
406 | // row was also marked for inclusion, merge this cell into it's box. | |||
407 | // Otherwise start a new box. | |||
408 | box = this.getCellBounds_([x, y]); | |||
409 | if (currentBox) { | |||
410 | currentBox.extend(box.getNorthEast()); | |||
411 | } else { | |||
412 | currentBox = box; | |||
413 | } | |||
414 | ||||
415 | } else { | |||
416 | // This cell is not marked for inclusion. If the previous cell was | |||
417 | // marked for inclusion, merge it's box with a box that spans the same | |||
418 | // columns from the row below if possible. | |||
419 | this.mergeBoxesY_(currentBox); | |||
420 | currentBox = null; | |||
421 | } | |||
422 | } | |||
423 | // If the last cell was marked for inclusion, merge it's box with a matching | |||
424 | // box from the row below if possible. | |||
425 | this.mergeBoxesY_(currentBox); | |||
426 | currentBox = null; | |||
427 | } | |||
428 | ||||
429 | // Traverse the grid a column at a time | |||
430 | for (x = 0; x < this.grid_.length; x++) { | |||
431 | for (y = 0; y < this.grid_[0].length; y++) { | |||
432 | if (this.grid_[x][y]) { | |||
433 | ||||
434 | // This cell is marked for inclusion. If the previous cell in this | |||
435 | // column was also marked for inclusion, merge this cell into it's box. | |||
436 | // Otherwise start a new box. | |||
437 | if (currentBox) { | |||
438 | box = this.getCellBounds_([x, y]); | |||
439 | currentBox.extend(box.getNorthEast()); | |||
440 | } else { | |||
441 | currentBox = this.getCellBounds_([x, y]); | |||
442 | } | |||
443 | ||||
444 | } else { | |||
445 | // This cell is not marked for inclusion. If the previous cell was | |||
446 | // marked for inclusion, merge it's box with a box that spans the same | |||
447 | // rows from the column to the left if possible. | |||
448 | this.mergeBoxesX_(currentBox); | |||
449 | currentBox = null; | |||
450 | ||||
451 | } | |||
452 | } | |||
453 | // If the last cell was marked for inclusion, merge it's box with a matching | |||
454 | // box from the column to the left if possible. | |||
455 | this.mergeBoxesX_(currentBox); | |||
456 | currentBox = null; | |||
457 | } | |||
458 | }; | |||
459 | ||||
460 | /** | |||
461 | * Search for an existing box in an adjacent row to the given box that spans the | |||
462 | * same set of columns and if one is found merge the given box into it. If one | |||
463 | * is not found, append this box to the list of existing boxes. | |||
464 | * | |||
465 | * @param {LatLngBounds} The box to merge | |||
466 | */ | |||
467 | RouteBoxer.prototype.mergeBoxesX_ = function (box) { | |||
468 | if (box !== null) { | |||
469 | for (var i = 0; i < this.boxesX_.length; i++) { | |||
470 | if (this.boxesX_[i].getNorthEast().lng() === box.getSouthWest().lng() && | |||
471 | this.boxesX_[i].getSouthWest().lat() === box.getSouthWest().lat() && | |||
472 | this.boxesX_[i].getNorthEast().lat() === box.getNorthEast().lat()) { | |||
473 | this.boxesX_[i].extend(box.getNorthEast()); | |||
474 | return; | |||
475 | } | |||
476 | } | |||
477 | this.boxesX_.push(box); | |||
478 | } | |||
479 | }; | |||
480 | ||||
481 | /** | |||
482 | * Search for an existing box in an adjacent column to the given box that spans | |||
483 | * the same set of rows and if one is found merge the given box into it. If one | |||
484 | * is not found, append this box to the list of existing boxes. | |||
485 | * | |||
486 | * @param {LatLngBounds} The box to merge | |||
487 | */ | |||
488 | RouteBoxer.prototype.mergeBoxesY_ = function (box) { | |||
489 | if (box !== null) { | |||
490 | for (var i = 0; i < this.boxesY_.length; i++) { | |||
491 | if (this.boxesY_[i].getNorthEast().lat() === box.getSouthWest().lat() && | |||
492 | this.boxesY_[i].getSouthWest().lng() === box.getSouthWest().lng() && | |||
493 | this.boxesY_[i].getNorthEast().lng() === box.getNorthEast().lng()) { | |||
494 | this.boxesY_[i].extend(box.getNorthEast()); | |||
495 | return; | |||
496 | } | |||
497 | } | |||
498 | this.boxesY_.push(box); | |||
499 | } | |||
500 | }; | |||
501 | ||||
502 | /** | |||
503 | * Obtain the LatLng of the origin of a cell on the grid | |||
504 | * | |||
505 | * @param {Number[]} cell The cell to lookup. | |||
506 | * @return {LatLng} The latlng of the origin of the cell. | |||
507 | */ | |||
508 | RouteBoxer.prototype.getCellBounds_ = function (cell) { | |||
509 | return new google.maps.LatLngBounds( | |||
510 | new google.maps.LatLng(this.latGrid_[cell[1]], this.lngGrid_[cell[0]]), | |||
511 | new google.maps.LatLng(this.latGrid_[cell[1] + 1], this.lngGrid_[cell[0] + 1])); | |||
512 | }; | |||
513 | ||||
514 | ||||
515 | RouteBoxer.prototype.initGoogle = function() { | |||
516 | /* Based on the Latitude/longitude spherical geodesy formulae & scripts | |||
517 | at http://www.movable-type.co.uk/scripts/latlong.html | |||
518 | (c) Chris Veness 2002-2010 | |||
519 | */ | |||
520 | google.maps.LatLng.prototype.rhumbDestinationPoint = function (brng, dist) { | |||
521 | var R = 6371; // earth's mean radius in km | |||
522 | var d = parseFloat(dist) / R; // d = angular distance covered on earth's surface | |||
523 | var lat1 = this.lat().toRad(), lon1 = this.lng().toRad(); | |||
524 | brng = brng.toRad(); | |||
525 | ||||
526 | var lat2 = lat1 + d * Math.cos(brng); | |||
527 | var dLat = lat2 - lat1; | |||
528 | var dPhi = Math.log(Math.tan(lat2 / 2 + Math.PI / 4) / Math.tan(lat1 / 2 + Math.PI / 4)); | |||
529 | var q = (Math.abs(dLat) > 1e-10) ? dLat / dPhi : Math.cos(lat1); | |||
530 | var dLon = d * Math.sin(brng) / q; | |||
531 | // check for going past the pole | |||
532 | if (Math.abs(lat2) > Math.PI / 2) { | |||
533 | lat2 = lat2 > 0 ? Math.PI - lat2 : - (Math.PI - lat2); | |||
534 | } | |||
535 | var lon2 = (lon1 + dLon + Math.PI) % (2 * Math.PI) - Math.PI; | |||
536 | ||||
537 | if (isNaN(lat2) || isNaN(lon2)) { | |||
538 | return null; | |||
539 | } | |||
540 | return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg()); | |||
541 | }; | |||
542 | ||||
543 | google.maps.LatLng.prototype.rhumbBearingTo = function (dest) { | |||
544 | var dLon = (dest.lng() - this.lng()).toRad(); | |||
545 | var dPhi = Math.log(Math.tan(dest.lat().toRad() / 2 + Math.PI / 4) / Math.tan(this.lat().toRad() / 2 + Math.PI / 4)); | |||
546 | if (Math.abs(dLon) > Math.PI) { | |||
547 | dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); | |||
548 | } | |||
549 | return Math.atan2(dLon, dPhi).toBrng(); | |||
550 | }; | |||
551 | ||||
552 | }; | |||
553 | ||||
554 | /** | |||
555 | * Extend the Number object to convert degrees to radians | |||
556 | * | |||
557 | * @return {Number} Bearing in radians | |||
558 | * @ignore | |||
559 | */ | |||
560 | Number.prototype.toRad = function () { | |||
561 | return this * Math.PI / 180; | |||
562 | }; | |||
563 | ||||
564 | /** | |||
565 | * Extend the Number object to convert radians to degrees | |||
566 | * | |||
567 | * @return {Number} Bearing in degrees | |||
568 | * @ignore | |||
569 | */ | |||
570 | Number.prototype.toDeg = function () { | |||
571 | return this * 180 / Math.PI; | |||
572 | }; | |||
573 | ||||
574 | /** | |||
575 | * Normalize a heading in degrees to between 0 and +360 | |||
576 | * | |||
577 | * @return {Number} Return | |||
578 | * @ignore | |||
579 | */ | |||
580 | Number.prototype.toBrng = function () { | |||
581 | return (this.toDeg() + 360) % 360; | |||
582 | }; | |||
/** | 1 | |||
* @name RouteBoxer | 2 | |||
* @version 1.0 | 3 | |||
* @copyright (c) 2010 Google Inc. | 4 | |||
* @author Thor Mitchell | 5 | |||
* | 6 | |||
* @fileoverview The RouteBoxer class takes a path, such as the Polyline for a | 7 | |||
* route generated by a Directions request, and generates a set of LatLngBounds | 8 | |||
* objects that are guaranteed to contain every point within a given distance | 9 | |||
* of that route. These LatLngBounds objects can then be used to generate | 10 | |||
* requests to spatial search services that support bounds filtering (such as | 11 | |||
* the Google Maps Data API) in order to implement search along a route. | 12 | |||
* <br/><br/> | 13 | |||
* RouteBoxer overlays a grid of the specified size on the route, identifies | 14 | |||
* every grid cell that the route passes through, and generates a set of bounds | 15 | |||
* that cover all of these cells, and their nearest neighbours. Consequently | 16 | |||
* the bounds returned will extend up to ~3x the specified distance from the | 17 | |||
* route in places. | 18 | |||
*/ | 19 | |||
20 | ||||
/* | 21 | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | 22 | |||
* you may not use this file except in compliance with the License. | 23 | |||
* You may obtain a copy of the License at | 24 | |||
* | 25 | |||
* http://www.apache.org/licenses/LICENSE-2.0 | 26 | |||
* | 27 | |||
* Unless required by applicable law or agreed to in writing, software | 28 | |||
* distributed under the License is distributed on an "AS IS" BASIS, | 29 | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 30 | |||
* See the License for the specific language governing permissions and | 31 | |||
* limitations under the License. | 32 | |||
*/ | 33 | |||
34 | ||||
/** | 35 | |||
* Creates a new RouteBoxer | 36 | |||
* | 37 | |||
* @constructor | 38 | |||
*/ | 39 | |||
function RouteBoxer() { | 40 | |||
this.R = 6371; // earth's mean radius in km | 41 | |||
this.initGoogle(); | 42 | |||
} | 43 | |||
44 | ||||
/** | 45 | |||
* Generates boxes for a given route and distance | 46 | |||
* | 47 | |||
* @param {google.maps.LatLng[] | google.maps.Polyline} path The path along | 48 | |||
* which to create boxes. The path object can be either an Array of | 49 | |||
* google.maps.LatLng objects or a Maps API v2 or Maps API v3 | 50 | |||
* google.maps.Polyline object. | 51 | |||
* @param {Number} range The distance in kms around the route that the generated | 52 | |||
* boxes must cover. | 53 | |||
* @return {google.maps.LatLngBounds[]} An array of boxes that covers the whole | 54 | |||
* path. | 55 | |||
*/ | 56 | |||
RouteBoxer.prototype.box = function (path, range) { | 57 | |||
// Two dimensional array representing the cells in the grid overlaid on the path | 58 | |||
this.grid_ = null; | 59 | |||
60 | ||||
// Array that holds the latitude coordinate of each vertical grid line | 61 | |||
this.latGrid_ = []; | 62 | |||
63 | ||||
// Array that holds the longitude coordinate of each horizontal grid line | 64 | |||
this.lngGrid_ = []; | 65 | |||
66 | ||||
// Array of bounds that cover the whole route formed by merging cells that | 67 | |||
// the route intersects first horizontally, and then vertically | 68 | |||
this.boxesX_ = []; | 69 | |||
70 | ||||
// Array of bounds that cover the whole route formed by merging cells that | 71 | |||
// the route intersects first vertically, and then horizontally | 72 | |||
this.boxesY_ = []; | 73 | |||
74 | ||||
// The array of LatLngs representing the vertices of the path | 75 | |||
var vertices = null; | 76 | |||
77 | ||||
// If necessary convert the path into an array of LatLng objects | 78 | |||
if (path instanceof Array) { | 79 | |||
// already an arry of LatLngs (eg. v3 overview_path) | 80 | |||
vertices = path; | 81 | |||
} else if (path instanceof google.maps.Polyline) { | 82 | |||
if (path.getPath) { | 83 | |||
// v3 Maps API Polyline object | 84 | |||
vertices = new Array(path.getPath().getLength()); | 85 | |||
for (var i = 0; i < vertices.length; i++) { | 86 | |||
vertices[i] = path.getPath().getAt(i); | 87 | |||
} | 88 | |||
} else { | 89 | |||
// v2 Maps API Polyline object | 90 | |||
vertices = new Array(path.getVertexCount()); | 91 | |||
for (var j = 0; j < vertices.length; j++) { | 92 | |||
vertices[j] = path.getVertex(j); | 93 | |||
} | 94 | |||
} | 95 | |||
} | 96 | |||
97 | ||||
// Build the grid that is overlaid on the route | 98 | |||
this.buildGrid_(vertices, range); | 99 | |||
100 | ||||
// Identify the grid cells that the route intersects | 101 | |||
this.findIntersectingCells_(vertices); | 102 | |||
103 | ||||
// Merge adjacent intersected grid cells (and their neighbours) into two sets | 104 | |||
// of bounds, both of which cover them completely | 105 | |||
this.mergeIntersectingCells_(); | 106 | |||
107 | ||||
// Return the set of merged bounds that has the fewest elements | 108 | |||
return (this.boxesX_.length <= this.boxesY_.length ? | 109 | |||
this.boxesX_ : | 110 | |||
this.boxesY_); | 111 | |||
}; | 112 | |||
113 | ||||
/** | 114 | |||
* Generates boxes for a given route and distance | 115 | |||
* | 116 | |||
* @param {LatLng[]} vertices The vertices of the path over which to lay the grid | 117 | |||
* @param {Number} range The spacing of the grid cells. | 118 | |||
*/ | 119 | |||
RouteBoxer.prototype.buildGrid_ = function (vertices, range) { | 120 | |||
121 | ||||
// Create a LatLngBounds object that contains the whole path | 122 | |||
var routeBounds = new google.maps.LatLngBounds(); | 123 | |||
for (var i = 0; i < vertices.length; i++) { | 124 | |||
routeBounds.extend(vertices[i]); | 125 | |||
} | 126 | |||
127 | ||||
// Find the center of the bounding box of the path | 128 | |||
var routeBoundsCenter = routeBounds.getCenter(); | 129 | |||
130 | ||||
// Starting from the center define grid lines outwards vertically until they | 131 | |||
// extend beyond the edge of the bounding box by more than one cell | 132 | |||
this.latGrid_.push(routeBoundsCenter.lat()); | 133 | |||
134 | ||||
// Add lines from the center out to the north | 135 | |||
this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range).lat()); | 136 | |||
for (i = 2; this.latGrid_[i - 2] < routeBounds.getNorthEast().lat(); i++) { | 137 | |||
this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range * i).lat()); | 138 | |||
} | 139 | |||
140 | ||||
// Add lines from the center out to the south | 141 | |||
for (i = 1; this.latGrid_[1] > routeBounds.getSouthWest().lat(); i++) { | 142 | |||
this.latGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(180, range * i).lat()); | 143 | |||
} | 144 | |||
145 | ||||
// Starting from the center define grid lines outwards horizontally until they | 146 | |||
// extend beyond the edge of the bounding box by more than one cell | 147 | |||
this.lngGrid_.push(routeBoundsCenter.lng()); | 148 | |||
149 | ||||
// Add lines from the center out to the east | 150 | |||
this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range).lng()); | 151 | |||
for (i = 2; this.lngGrid_[i - 2] < routeBounds.getNorthEast().lng(); i++) { | 152 | |||
this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range * i).lng()); | 153 | |||
} | 154 | |||
155 | ||||
// Add lines from the center out to the west | 156 | |||
for (i = 1; this.lngGrid_[1] > routeBounds.getSouthWest().lng(); i++) { | 157 | |||
this.lngGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(270, range * i).lng()); | 158 | |||
} | 159 | |||
160 | ||||
// Create a two dimensional array representing this grid | 161 | |||
this.grid_ = new Array(this.lngGrid_.length); | 162 | |||
for (i = 0; i < this.grid_.length; i++) { | 163 | |||
this.grid_[i] = new Array(this.latGrid_.length); | 164 | |||
} | 165 | |||
}; | 166 | |||
167 | ||||
/** | 168 | |||
* Find all of the cells in the overlaid grid that the path intersects | 169 | |||
* | 170 | |||
* @param {LatLng[]} vertices The vertices of the path | 171 | |||
*/ | 172 | |||
RouteBoxer.prototype.findIntersectingCells_ = function (vertices) { | 173 | |||
// Find the cell where the path begins | 174 | |||
var hintXY = this.getCellCoords_(vertices[0]); | 175 | |||
176 | ||||
// Mark that cell and it's neighbours for inclusion in the boxes | 177 | |||
this.markCell_(hintXY); | 178 | |||
179 | ||||
// Work through each vertex on the path identifying which grid cell it is in | 180 | |||
for (var i = 1; i < vertices.length; i++) { | 181 | |||
// Use the known cell of the previous vertex to help find the cell of this vertex | 182 | |||
var gridXY = this.getGridCoordsFromHint_(vertices[i], vertices[i - 1], hintXY); | 183 | |||
184 | ||||
if (gridXY[0] === hintXY[0] && gridXY[1] === hintXY[1]) { | 185 | |||
// This vertex is in the same cell as the previous vertex | 186 | |||
// The cell will already have been marked for inclusion in the boxes | 187 | |||
continue; | 188 | |||
189 | ||||
} else if ((Math.abs(hintXY[0] - gridXY[0]) === 1 && hintXY[1] === gridXY[1]) || | 190 | |||
(hintXY[0] === gridXY[0] && Math.abs(hintXY[1] - gridXY[1]) === 1)) { | 191 | |||
// This vertex is in a cell that shares an edge with the previous cell | 192 | |||
// Mark this cell and it's neighbours for inclusion in the boxes | 193 | |||
this.markCell_(gridXY); | 194 | |||
195 | ||||
} else { | 196 | |||
// This vertex is in a cell that does not share an edge with the previous | 197 | |||
// cell. This means that the path passes through other cells between | 198 | |||
// this vertex and the previous vertex, and we must determine which cells | 199 | |||
// it passes through | 200 | |||
this.getGridIntersects_(vertices[i - 1], vertices[i], hintXY, gridXY); | 201 | |||
} | 202 | |||
203 | ||||
// Use this cell to find and compare with the next one | 204 | |||
hintXY = gridXY; | 205 | |||
} | 206 | |||
}; | 207 | |||
208 | ||||
/** | 209 | |||
* Find the cell a path vertex is in by brute force iteration over the grid | 210 | |||
* | 211 | |||
* @param {LatLng[]} latlng The latlng of the vertex | 212 | |||
* @return {Number[][]} The cell coordinates of this vertex in the grid | 213 | |||
*/ | 214 | |||
RouteBoxer.prototype.getCellCoords_ = function (latlng) { | 215 | |||
for (var x = 0; this.lngGrid_[x] < latlng.lng(); x++) {} | 216 | |||
for (var y = 0; this.latGrid_[y] < latlng.lat(); y++) {} | 217 | |||
return ([x - 1, y - 1]); | 218 | |||
}; | 219 | |||
220 | ||||
/** | 221 | |||
* Find the cell a path vertex is in based on the known location of a nearby | 222 | |||
* vertex. This saves searching the whole grid when working through vertices | 223 | |||
* on the polyline that are likely to be in close proximity to each other. | 224 | |||
* | 225 | |||
* @param {LatLng[]} latlng The latlng of the vertex to locate in the grid | 226 | |||
* @param {LatLng[]} hintlatlng The latlng of the vertex with a known location | 227 | |||
* @param {Number[]} hint The cell containing the vertex with a known location | 228 | |||
* @return {Number[]} The cell coordinates of the vertex to locate in the grid | 229 | |||
*/ | 230 | |||
RouteBoxer.prototype.getGridCoordsFromHint_ = function (latlng, hintlatlng, hint) { | 231 | |||
var x, y; | 232 | |||
if (latlng.lng() > hintlatlng.lng()) { | 233 | |||
for (x = hint[0]; this.lngGrid_[x + 1] < latlng.lng(); x++) {} | 234 | |||
} else { | 235 | |||
for (x = hint[0]; this.lngGrid_[x] > latlng.lng(); x--) {} | 236 | |||
} | 237 | |||
238 | ||||
if (latlng.lat() > hintlatlng.lat()) { | 239 | |||
for (y = hint[1]; this.latGrid_[y + 1] < latlng.lat(); y++) {} | 240 | |||
} else { | 241 | |||
for (y = hint[1]; this.latGrid_[y] > latlng.lat(); y--) {} | 242 | |||
} | 243 | |||
244 | ||||
return ([x, y]); | 245 | |||
}; | 246 | |||
247 | ||||
248 | ||||
/** | 249 | |||
* Identify the grid squares that a path segment between two vertices | 250 | |||
* intersects with by: | 251 | |||
* 1. Finding the bearing between the start and end of the segment | 252 | |||
* 2. Using the delta between the lat of the start and the lat of each | 253 | |||
* latGrid boundary to find the distance to each latGrid boundary | 254 | |||
* 3. Finding the lng of the intersection of the line with each latGrid | 255 | |||
* boundary using the distance to the intersection and bearing of the line | 256 | |||
* 4. Determining the x-coord on the grid of the point of intersection | 257 | |||
* 5. Filling in all squares between the x-coord of the previous intersection | 258 | |||
* (or start) and the current one (or end) at the current y coordinate, | 259 | |||
* which is known for the grid line being intersected | 260 | |||
* | 261 | |||
* @param {LatLng} start The latlng of the vertex at the start of the segment | 262 | |||
* @param {LatLng} end The latlng of the vertex at the end of the segment | 263 | |||
* @param {Number[]} startXY The cell containing the start vertex | 264 | |||
* @param {Number[]} endXY The cell containing the vend vertex | 265 | |||
*/ | 266 | |||
RouteBoxer.prototype.getGridIntersects_ = function (start, end, startXY, endXY) { | 267 | |||
var edgePoint, edgeXY, i; | 268 | |||
var brng = start.rhumbBearingTo(end); // Step 1. | 269 | |||
270 | ||||
var hint = start; | 271 | |||
var hintXY = startXY; | 272 | |||
273 | ||||
// Handle a line segment that travels south first | 274 | |||
if (end.lat() > start.lat()) { | 275 | |||
// Iterate over the east to west grid lines between the start and end cells | 276 | |||
for (i = startXY[1] + 1; i <= endXY[1]; i++) { | 277 | |||
// Find the latlng of the point where the path segment intersects with | 278 | |||
// this grid line (Step 2 & 3) | 279 | |||
edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); | 280 | |||
281 | ||||
// Find the cell containing this intersect point (Step 4) | 282 | |||
edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); | 283 | |||
284 | ||||
// Mark every cell the path has crossed between this grid and the start, | 285 | |||
// or the previous east to west grid line it crossed (Step 5) | 286 | |||
this.fillInGridSquares_(hintXY[0], edgeXY[0], i - 1); | 287 | |||
288 | ||||
// Use the point where it crossed this grid line as the reference for the | 289 | |||
// next iteration | 290 | |||
hint = edgePoint; | 291 | |||
hintXY = edgeXY; | 292 | |||
} | 293 | |||
294 | ||||
// Mark every cell the path has crossed between the last east to west grid | 295 | |||
// line it crossed and the end (Step 5) | 296 | |||
this.fillInGridSquares_(hintXY[0], endXY[0], i - 1); | 297 | |||
298 | ||||
} else { | 299 | |||
// Iterate over the east to west grid lines between the start and end cells | 300 | |||
for (i = startXY[1]; i > endXY[1]; i--) { | 301 | |||
// Find the latlng of the point where the path segment intersects with | 302 | |||
// this grid line (Step 2 & 3) | 303 | |||
edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); | 304 | |||
305 | ||||
// Find the cell containing this intersect point (Step 4) | 306 | |||
edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); | 307 | |||
308 | ||||
// Mark every cell the path has crossed between this grid and the start, | 309 | |||
// or the previous east to west grid line it crossed (Step 5) | 310 | |||
this.fillInGridSquares_(hintXY[0], edgeXY[0], i); | 311 | |||
312 | ||||
// Use the point where it crossed this grid line as the reference for the | 313 | |||
// next iteration | 314 | |||
hint = edgePoint; | 315 | |||
hintXY = edgeXY; | 316 | |||
} | 317 | |||
318 | ||||
// Mark every cell the path has crossed between the last east to west grid | 319 | |||
// line it crossed and the end (Step 5) | 320 | |||
this.fillInGridSquares_(hintXY[0], endXY[0], i); | 321 | |||
322 | ||||
} | 323 | |||
}; | 324 | |||
325 | ||||
/** | 326 | |||
* Find the latlng at which a path segment intersects with a given | 327 | |||
* line of latitude | 328 | |||
* | 329 | |||
* @param {LatLng} start The vertex at the start of the path segment | 330 | |||
* @param {Number} brng The bearing of the line from start to end | 331 | |||
* @param {Number} gridLineLat The latitude of the grid line being intersected | 332 | |||
* @return {LatLng} The latlng of the point where the path segment intersects | 333 | |||
* the grid line | 334 | |||
*/ | 335 | |||
RouteBoxer.prototype.getGridIntersect_ = function (start, brng, gridLineLat) { | 336 | |||
var d = this.R * ((gridLineLat.toRad() - start.lat().toRad()) / Math.cos(brng.toRad())); | 337 | |||
return start.rhumbDestinationPoint(brng, d); | 338 | |||
}; | 339 | |||
340 | ||||
/** | 341 | |||
* Mark all cells in a given row of the grid that lie between two columns | 342 | |||
* for inclusion in the boxes | 343 | |||
* | 344 | |||
* @param {Number} startx The first column to include | 345 | |||
* @param {Number} endx The last column to include | 346 | |||
* @param {Number} y The row of the cells to include | 347 | |||
*/ | 348 | |||
RouteBoxer.prototype.fillInGridSquares_ = function (startx, endx, y) { | 349 | |||
var x; | 350 | |||
if (startx < endx) { | 351 | |||
for (x = startx; x <= endx; x++) { | 352 | |||
this.markCell_([x, y]); | 353 | |||
} | 354 | |||
} else { | 355 | |||
for (x = startx; x >= endx; x--) { | 356 | |||
this.markCell_([x, y]); | 357 | |||
} | 358 | |||
} | 359 | |||
}; | 360 | |||
361 | ||||
/** | 362 | |||
* Mark a cell and the 8 immediate neighbours for inclusion in the boxes | 363 | |||
* | 364 | |||
* @param {Number[]} square The cell to mark | 365 | |||
*/ | 366 | |||
RouteBoxer.prototype.markCell_ = function (cell) { | 367 | |||
var x = cell[0]; | 368 | |||
var y = cell[1]; | 369 | |||
this.grid_[x - 1][y - 1] = 1; | 370 | |||
this.grid_[x][y - 1] = 1; | 371 | |||
this.grid_[x + 1][y - 1] = 1; | 372 | |||
this.grid_[x - 1][y] = 1; | 373 | |||
this.grid_[x][y] = 1; | 374 | |||
this.grid_[x + 1][y] = 1; | 375 | |||
this.grid_[x - 1][y + 1] = 1; | 376 | |||
this.grid_[x][y + 1] = 1; | 377 | |||
this.grid_[x + 1][y + 1] = 1; | 378 | |||
}; | 379 | |||
380 | ||||
/** | 381 | |||
* Create two sets of bounding boxes, both of which cover all of the cells that | 382 | |||
* have been marked for inclusion. | 383 | |||
* | 384 | |||
* The first set is created by combining adjacent cells in the same column into | 385 | |||
* a set of vertical rectangular boxes, and then combining boxes of the same | 386 | |||
* height that are adjacent horizontally. | 387 | |||
* | 388 | |||
* The second set is created by combining adjacent cells in the same row into | 389 | |||
* a set of horizontal rectangular boxes, and then combining boxes of the same | 390 | |||
* width that are adjacent vertically. | 391 | |||
* | 392 | |||
*/ | 393 | |||
RouteBoxer.prototype.mergeIntersectingCells_ = function () { | 394 | |||
var x, y, box; | 395 | |||
396 | ||||
// The box we are currently expanding with new cells | 397 | |||
var currentBox = null; | 398 | |||
399 | ||||
// Traverse the grid a row at a time | 400 | |||
for (y = 0; y < this.grid_[0].length; y++) { | 401 | |||
for (x = 0; x < this.grid_.length; x++) { | 402 | |||
403 | ||||
if (this.grid_[x][y]) { | 404 | |||
// This cell is marked for inclusion. If the previous cell in this | 405 | |||
// row was also marked for inclusion, merge this cell into it's box. | 406 | |||
// Otherwise start a new box. | 407 | |||
box = this.getCellBounds_([x, y]); | 408 | |||
if (currentBox) { | 409 | |||
currentBox.extend(box.getNorthEast()); | 410 | |||
} else { | 411 | |||
currentBox = box; | 412 | |||
} | 413 | |||
414 | ||||
} else { | 415 | |||
// This cell is not marked for inclusion. If the previous cell was | 416 | |||
// marked for inclusion, merge it's box with a box that spans the same | 417 | |||
// columns from the row below if possible. | 418 | |||
this.mergeBoxesY_(currentBox); | 419 | |||
currentBox = null; | 420 | |||
} | 421 | |||
} | 422 | |||
// If the last cell was marked for inclusion, merge it's box with a matching | 423 | |||
// box from the row below if possible. | 424 | |||
this.mergeBoxesY_(currentBox); | 425 | |||
currentBox = null; | 426 | |||
} | 427 | |||
428 | ||||
// Traverse the grid a column at a time | 429 | |||
for (x = 0; x < this.grid_.length; x++) { | 430 | |||
for (y = 0; y < this.grid_[0].length; y++) { | 431 | |||
if (this.grid_[x][y]) { | 432 | |||
433 | ||||
// This cell is marked for inclusion. If the previous cell in this | 434 | |||
// column was also marked for inclusion, merge this cell into it's box. | 435 | |||
// Otherwise start a new box. | 436 | |||
if (currentBox) { | 437 | |||
box = this.getCellBounds_([x, y]); | 438 | |||
currentBox.extend(box.getNorthEast()); | 439 | |||
} else { | 440 | |||
currentBox = this.getCellBounds_([x, y]); | 441 | |||
} | 442 | |||
443 | ||||
} else { | 444 | |||
// This cell is not marked for inclusion. If the previous cell was | 445 | |||
// marked for inclusion, merge it's box with a box that spans the same | 446 | |||
// rows from the column to the left if possible. | 447 | |||
this.mergeBoxesX_(currentBox); | 448 | |||
currentBox = null; | 449 | |||
450 | ||||
} | 451 | |||
} | 452 | |||
// If the last cell was marked for inclusion, merge it's box with a matching | 453 | |||
// box from the column to the left if possible. | 454 | |||
this.mergeBoxesX_(currentBox); | 455 | |||
currentBox = null; | 456 | |||
} | 457 | |||
}; | 458 | |||
459 | ||||
/** | 460 | |||
* Search for an existing box in an adjacent row to the given box that spans the | 461 | |||
* same set of columns and if one is found merge the given box into it. If one | 462 | |||
* is not found, append this box to the list of existing boxes. | 463 | |||
* | 464 | |||
* @param {LatLngBounds} The box to merge | 465 | |||
*/ | 466 | |||
RouteBoxer.prototype.mergeBoxesX_ = function (box) { | 467 | |||
if (box !== null) { | 468 | |||
for (var i = 0; i < this.boxesX_.length; i++) { | 469 | |||
if (this.boxesX_[i].getNorthEast().lng() === box.getSouthWest().lng() && | 470 | |||
this.boxesX_[i].getSouthWest().lat() === box.getSouthWest().lat() && | 471 | |||
this.boxesX_[i].getNorthEast().lat() === box.getNorthEast().lat()) { | 472 | |||
this.boxesX_[i].extend(box.getNorthEast()); | 473 | |||
return; | 474 | |||
} | 475 | |||
} | 476 | |||
this.boxesX_.push(box); | 477 | |||
} | 478 | |||
}; | 479 | |||
480 | ||||
/** | 481 | |||
* Search for an existing box in an adjacent column to the given box that spans | 482 | |||
* the same set of rows and if one is found merge the given box into it. If one | 483 | |||
* is not found, append this box to the list of existing boxes. | 484 | |||
* | 485 | |||
* @param {LatLngBounds} The box to merge | 486 | |||
*/ | 487 | |||
RouteBoxer.prototype.mergeBoxesY_ = function (box) { | 488 | |||
if (box !== null) { | 489 | |||
for (var i = 0; i < this.boxesY_.length; i++) { | 490 | |||
if (this.boxesY_[i].getNorthEast().lat() === box.getSouthWest().lat() && | 491 | |||
this.boxesY_[i].getSouthWest().lng() === box.getSouthWest().lng() && | 492 | |||
this.boxesY_[i].getNorthEast().lng() === box.getNorthEast().lng()) { | 493 | |||
this.boxesY_[i].extend(box.getNorthEast()); | 494 | |||
return; | 495 | |||
} | 496 | |||
} | 497 | |||
this.boxesY_.push(box); | 498 | |||
} | 499 | |||
}; | 500 | |||
501 | ||||
/** | 502 | |||
* Obtain the LatLng of the origin of a cell on the grid | 503 | |||
* | 504 | |||
* @param {Number[]} cell The cell to lookup. | 505 | |||
* @return {LatLng} The latlng of the origin of the cell. | 506 | |||
*/ | 507 | |||
RouteBoxer.prototype.getCellBounds_ = function (cell) { | 508 | |||
return new google.maps.LatLngBounds( | 509 | |||
new google.maps.LatLng(this.latGrid_[cell[1]], this.lngGrid_[cell[0]]), | 510 |
public/stylesheets/main.css
View file @
77ade48
1 | html, body { | |||
2 | height: 100%; | |||
3 | width: 100%; | |||
4 | } | |||
5 | ||||
html, body { | 1 | 6 | #alt-routes { | |
height: 100%; | 2 | 7 | height: 75px; | |
width: 100%; | 3 | 8 | } | |
} | 4 | 9 | ||
5 | 10 | #map { | ||
#alt-routes { | 6 | 11 | height: 600px; | |
height: 75px; | 7 | 12 | } | |
13 | ||||
14 | #map-area { | |||
15 | height: 600px; | |||
} | 8 | 16 | width: 100%; | |
9 | 17 | } | ||
#map { | 10 | 18 | ||
height: 600px; | 11 | 19 | #map-top { | |
} | 12 | 20 | margin-bottom: 30px; | |
13 | 21 | } | ||
#map-area { | 14 | 22 | ||
23 | #right-panel { | |||
24 | height: 600px; | |||
25 | /*float: right; | |||
26 | width: 40%;*/ | |||
27 | overflow: auto; | |||
28 | } | |||
29 | ||||
height: 600px; | 15 | 30 | .img-responsive { | |
width: 100%; | 16 | 31 | margin: 0 auto; | |
} | 17 | 32 | } | |
18 | 33 | |||
#map-top { | 19 | 34 | .status { | |
margin-bottom: 30px; | 20 | 35 | margin-top: 5px; | |
} | 21 | 36 | } | |
22 | 37 | |||
#right-panel { | 23 | 38 | .text-field { | |
height: 600px; | 24 | 39 | margin-top: 30px; | |
/*float: right; | 25 | 40 | margin-bottom: 30px; | |
width: 40%;*/ | 26 | 41 | } | |
overflow: auto; | 27 | 42 | ||
} | 28 | 43 | .center-this { | |
29 | 44 | margin: 0 auto; | ||
.img-responsive { | 30 | 45 | width: 100%; | |
margin: 0 auto; | 31 | 46 | } | |
} | 32 | 47 | ||
33 | 48 | .btn-space{ | ||
.status { | 34 | 49 | margin-top: 10px; | |
margin-top: 5px; | 35 | 50 | width: 50%; | |
} | 36 | 51 | } | |
37 | 52 | |||
.text-field { | 38 | 53 | .log-in-textfield{ | |
margin-top: 30px; | 39 | 54 | width: 30%; | |
margin-bottom: 30px; | 40 | |||
} | 41 | 55 | } | |
56 | ||||
57 | #floating-panel { | |||
58 | position: absolute; | |||
59 | top: 10px; | |||
60 | left: 25%; | |||
61 | z-index: 5; | |||
62 | background-color: #fff; | |||
63 | padding: 5px; | |||
64 | border: 1px solid #999; | |||
65 | text-align: center; | |||
66 | font-family: 'Roboto','sans-serif'; | |||
67 | line-height: 30px; | |||
68 | padding-left: 10px; | |||
69 | } | |||
70 | #floating-panel { | |||
71 | background-color: #fff; | |||
72 | border: 1px solid #999; | |||
73 | left: 25%; | |||
74 | padding: 5px; | |||
75 | position: absolute; | |||
76 | top: 10px; | |||
77 | z-index: 5; | |||
78 | } | |||
79 | /** | |||
80 | */ | |||
81 | ||||
42 | ||||
.center-this { | 43 | |||
margin: 0 auto; | 44 | |||
width: 100%; | 45 | |||
} | 46 | |||
47 | ||||
.btn-space{ | 48 | |||
margin-top: 10px; | 49 | |||
width: 50%; | 50 | |||
} | 51 | |||
52 | ||||
.log-in-textfield{ | 53 | |||
width: 30%; | 54 | |||
} | 55 | |||
56 | ||||
#floating-panel { | 57 | |||
position: absolute; | 58 | |||
top: 10px; | 59 | |||
left: 25%; | 60 | |||
z-index: 5; | 61 | |||
background-color: #fff; | 62 | |||
padding: 5px; | 63 | |||
border: 1px solid #999; | 64 | |||
text-align: center; | 65 | |||
font-family: 'Roboto','sans-serif'; | 66 | |||
line-height: 30px; | 67 | |||
padding-left: 10px; | 68 | |||
} | 69 |
views/pages/index.ejs
View file @
77ade48
<!DOCTYPE html> | 1 | 1 | <!DOCTYPE html> | |
<html lang="en"> | 2 | 2 | <html lang="en"> | |
<head> | 3 | 3 | <head> | |
<% include ../partials/header %> | 4 | 4 | <% include ../partials/header %> | |
</head> | 5 | 5 | </head> | |
6 | 6 | |||
<!-- Homepage --> | 7 | 7 | <!-- Homepage --> | |
8 | 8 | |||
<body> | 9 | 9 | <body> | |
<div class="container-fluid"> | 10 | 10 | <div class="container-fluid"> | |
<div class="jumbotron text-center"> | 11 | 11 | <div class="jumbotron text-center"> | |
<h1>Safe Routes</h1> | 12 | 12 | <h1>Safe Routes</h1> | |
<p>The best way to find the safest routes.</p> | 13 | 13 | <p>The best way to find the safest routes.</p> | |
</div> | 14 | 14 | </div> | |
15 | 15 | |||
16 | 16 | |||
17 | 17 | |||
18 | 18 | |||
<% if(!locals.loggedin || (locals.loggedin != "true")){ %> | 19 | 19 | <% if(!locals.loggedin || (locals.loggedin != "true")){ %> | |
20 | 20 | |||
<%console.log(locals.loggedin)%> | 21 | 21 | <%console.log(locals.loggedin)%> | |
<form action="/login"> | 22 | 22 | <form action="/login"> | |
23 | 23 | |||
<button type="submit" class="btn btn-primary">Log In</button> | 24 | 24 | <button type="submit" class="btn btn-primary">Log In</button> | |
<!-- <input type="submit" value="Submit"> --> | 25 | 25 | <!-- <input type="submit" value="Submit"> --> | |
</form> | 26 | 26 | </form> | |
<% } %> | 27 | 27 | <% } %> | |
28 | 28 | |||
29 | 29 | |||
30 | 30 | |||
31 | 31 | |||
<!--What are for and type for?--> | 32 | 32 | <!--What are for and type for?--> | |
<!-- current submit button takes you to the landing page --> | 33 | 33 | <!-- current submit button takes you to the landing page --> | |
<form action="/landing"> | 34 | 34 | <form action="/landing"> | |
<!-- First text field and label above the text field for current location --> | 35 | 35 | <!-- First text field and label above the text field for current location --> | |
<div class="form-group text-center"> | 36 | 36 | <div class="form-group text-center"> | |
<label for="origin">From</label> | 37 | 37 | <label for="origin">From</label> | |
<input type="text" class="form-control" id="origin" placeholder="Enter Current Location" onclick="initFrom()"> | 38 | 38 | <input type="text" class="form-control" id="origin" placeholder="Enter Current Location" onclick="initFrom()"> | |
</div> | 39 | 39 | </div> | |
<!-- Second text field and label above the text field for destination --> | 40 | 40 | <!-- Second text field and label above the text field for destination --> | |
<div class="form-group text-center"> | 41 | 41 | <div class="form-group text-center"> | |
<label for="dest">To</label> | 42 | 42 | <label for="dest">To</label> | |
<input type="text" class="form-control" id="dest" placeholder="Enter Destination" onclick="initTo()"> | 43 | 43 | <input type="text" class="form-control" id="dest" placeholder="Enter Destination" onclick="initTo()"> | |
</div> | 44 | 44 | </div> | |
<!-- First travel option button, walking | 45 | 45 | <!-- First travel option button, walking | |
This contains a row that holds buttons of type button. Buttons are highlighted upon selection. | 46 | 46 | This contains a row that holds buttons of type button. Buttons are highlighted upon selection. | |
--> | 47 | 47 | --> | |
<div class="row"> | 48 | 48 | <div class="row"> | |
<button type="button" class="btn-default img-thumbnail col-xs-4" onclick="walk();"> | 49 | 49 | <button type="button" class="btn-default img-thumbnail col-xs-4" onclick="walk();"> | |
<img class="img-responsive" src="images/pedestrian-walking.svg" alt="Walking Option" width="40" height="40"> | 50 | 50 | <img class="img-responsive" src="images/pedestrian-walking.svg" alt="Walking Option" width="40" height="40"> | |
</button> | 51 | 51 | </button> | |
52 | 52 | |||
<!-- Second travel button, driving --> | 53 | 53 | <!-- Second travel button, driving --> | |
<button type="button" class="btn-default img-thumbnail col-xs-4" onclick="drive();"> | 54 | 54 | <button type="button" class="btn-default img-thumbnail col-xs-4" onclick="drive();"> | |
<img class="img-responsive" src="images/car-compact.svg" alt="Walking Option" width="40" height="40"> | 55 | 55 | <img class="img-responsive" src="images/car-compact.svg" alt="Walking Option" width="40" height="40"> | |
</button> | 56 | 56 | </button> | |
57 | 57 | |||
<!-- Third travel button, bus --> | 58 | 58 | <!-- Third travel button, bus --> | |
<button type="button" class="btn-default img-thumbnail col-xs-4" onclick="bus();"> | 59 | 59 | <button type="button" class="btn-default img-thumbnail col-xs-4" onclick="bus();"> | |
<img class="img-responsive" src="images/bus.svg" alt="Walking Option" width="40" height="40"> | 60 | 60 | <img class="img-responsive" src="images/bus.svg" alt="Walking Option" width="40" height="40"> | |
</button> | 61 | 61 | </button> | |
</div> | 62 | 62 | </div> | |
63 | 63 | |||
<div class="text-center"> | 64 | 64 | <div class="text-center"> | |
<button type="submit" class="btn btn-primary btn-space">Go</button> | 65 | 65 | <button type="submit" class="btn btn-primary btn-space">Go</button> | |
</div> | 66 | 66 | </div> | |
</form> | 67 | 67 | </form> | |
</div> | 68 | 68 | </div> | |
69 | 69 | |||
<!-- Google Map functionality. --> | 70 | 70 | <!-- Google Map functionality. --> | |
<script> | 71 | 71 | <script> | |
function initFrom() { | 72 | 72 | function initFrom() { | |
var temp = document.getElementById("origin"); | 73 | 73 | var temp = document.getElementById("origin"); | |
74 | 74 | |||
var autoOrigin = new google.maps.places.Autocomplete(temp); | 75 | 75 | var autoOrigin = new google.maps.places.Autocomplete(temp); | |
76 | 76 | |||
google.maps.event.addListener(autoOrigin, 'place_changed', function () { | 77 | 77 | google.maps.event.addListener(autoOrigin, 'place_changed', function () { | |
var place = autoOrigin.getPlace(); | 78 | 78 | var place = autoOrigin.getPlace(); | |
// Testing. | 79 | 79 | // Testing. | |
console.log(place); | 80 | 80 | console.log(place); | |
81 | <<<<<<< HEAD | |||
82 | //localStorage.setItem("from", place.formatted_address); | |||
83 | localStorage.setItem("fromIn", temp.value); | |||
84 | localStorage.setItem("originLat", place.geometry.location.lat()); | |||
85 | localStorage.setItem("originLng", place.geometry.location.lng()); | |||
86 | ======= | |||
//localStorage.setItem("from", place.formatted_address); | 81 | 87 | localStorage.setItem("from", place.formatted_address); | |
88 | >>>>>>> 044c2fcc67b88e2e59ea2720d1eb8ababb014660 | |||
localStorage.setItem("fromIn", temp.value); | 82 | 89 | }); | |
localStorage.setItem("originLat", place.geometry.location.lat()); | 83 | 90 | } | |
localStorage.setItem("originLng", place.geometry.location.lng()); | 84 | 91 | ||
}); | 85 | 92 | function initTo() { | |
} | 86 | 93 | var temp = document.getElementById("dest"); | |
87 | 94 | |||
function initTo() { | 88 | 95 | var autoDest = new google.maps.places.Autocomplete(temp); | |
var temp = document.getElementById("dest"); | 89 | 96 | ||
90 | 97 | google.maps.event.addListener(autoDest, 'place_changed', function () { | ||
var autoDest = new google.maps.places.Autocomplete(temp); | 91 | 98 | var place = autoDest.getPlace(); | |
92 | 99 | // Testing. | ||
google.maps.event.addListener(autoDest, 'place_changed', function () { | 93 | 100 | console.log(place); | |
101 | <<<<<<< HEAD | |||
102 | //localStorage.setItem("to", place.formatted_address); | |||
103 | localStorage.setItem("toIn", temp.value); | |||
104 | localStorage.setItem("destLat", place.geometry.location.lat()); | |||
105 | localStorage.setItem("destLng", place.geometry.location.lng()); | |||
106 | }); | |||
107 | } | |||
108 | ||||
109 | if (navigator.geolocation) { | |||
110 | navigator.geolocation.getCurrentPosition(function(pos) { | |||
111 | var geocoder = new google.maps.Geocoder; | |||
112 | var point = new google.maps.LatLng( | |||
113 | pos.coords.latitude, pos.coords.longitude); | |||
114 | ||||
115 | localStorage.setItem("originLat", pos.coords.latitude); | |||
116 | localStorage.setItem("originLng", pos.coords.longitude); | |||
117 | ||||
118 | geocoder.geocode({'latLng': point}, function (locations, status) { | |||
119 | if (status == google.maps.GeocoderStatus.OK) { | |||
120 | for (var location of locations) { | |||
121 | if ($.inArray("street_address", location.types) != -1) { | |||
122 | console.log('Your location is: ' + location.formatted_address); | |||
123 | document.getElementById("origin").value = location.formatted_address; | |||
124 | saveFrom(); | |||
125 | break; | |||
126 | } | |||
127 | }; | |||
128 | } | |||
129 | }); | |||
130 | ======= | |||
var place = autoDest.getPlace(); | 94 | 131 | localStorage.setItem("to", place.formatted_address); | |
132 | >>>>>>> 044c2fcc67b88e2e59ea2720d1eb8ababb014660 | |||
// Testing. | 95 | 133 | }); | |
console.log(place); | 96 | 134 | } | |
//localStorage.setItem("to", place.formatted_address); | 97 | 135 | ||
localStorage.setItem("toIn", temp.value); | 98 | 136 | function saveFrom() { | |
localStorage.setItem("destLat", place.geometry.location.lat()); | 99 | 137 | var fromInput = document.getElementById("origin").value; | |
localStorage.setItem("destLng", place.geometry.location.lng()); | 100 | 138 | localStorage.setItem("from", fromInput); | |
139 | <<<<<<< HEAD | |||
140 | localStorage.setItem("fromIn", fromInput); | |||
141 | ======= | |||
142 | >>>>>>> 044c2fcc67b88e2e59ea2720d1eb8ababb014660 | |||
}); | 101 | 143 | } | |
} | 102 | 144 | function saveTo() { | |
103 | 145 | var toInput = document.getElementById("dest").value; | ||
if (navigator.geolocation) { | 104 | 146 | localStorage.setItem("to", toInput); | |
navigator.geolocation.getCurrentPosition(function(pos) { | 105 | 147 | } | |
var geocoder = new google.maps.Geocoder; | 106 | 148 | function walk() { | |
var point = new google.maps.LatLng( | 107 | 149 | localStorage.setItem("mode", "WALKING"); | |
pos.coords.latitude, pos.coords.longitude); | 108 | 150 | } | |
109 | 151 | function drive() { | ||
localStorage.setItem("originLat", pos.coords.latitude); | 110 | 152 | localStorage.setItem("mode", "DRIVING"); | |
localStorage.setItem("originLng", pos.coords.longitude); | 111 | 153 | } | |
112 | 154 | function bus() { | ||
geocoder.geocode({'latLng': point}, function (locations, status) { | 113 | 155 | localStorage.setItem("mode", "TRANSIT"); | |
if (status == google.maps.GeocoderStatus.OK) { | 114 | 156 | } | |
for (var location of locations) { | 115 | 157 | ||
if ($.inArray("street_address", location.types) != -1) { | 116 | 158 | function login(){ | |
console.log('Your location is: ' + location.formatted_address); | 117 | 159 | ||
document.getElementById("origin").value = location.formatted_address; | 118 | 160 | } | |
saveFrom(); | 119 | 161 | </script> | |
break; | 120 | 162 | ||
} | 121 | 163 | <script async defer | |
}; | 122 | 164 | src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDAXDHeZXEv4H4ZnThVDxpyAuxVpzOcj_U&libraries=places"> | |
} | 123 | 165 | </script> | |
}); | 124 | 166 | ||
}); | 125 | 167 | </body> | |
} | 126 | 168 | </html> | |
127 | ||||
function saveFrom() { | 128 |
views/pages/landing.ejs
View file @
77ade48
<!DOCTYPE html> | 1 | 1 | <!DOCTYPE html> | |
<html lang="en"> | 2 | 2 | <html lang="en"> | |
<head> | 3 | 3 | <head> | |
<% include ../partials/header %> | 4 | 4 | <% include ../partials/header %> | |
<!-- <script type="text/javascript" | 5 | 5 | <!-- <script type="text/javascript" | |
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDAXDHeZXEv4H4ZnThVDxpyAuxVpzOcj_U&libraries=visualization"> | 6 | 6 | src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDAXDHeZXEv4H4ZnThVDxpyAuxVpzOcj_U&libraries=visualization"> | |
</script> --> | 7 | 7 | </script> --> | |
8 | 8 | |||
9 | <script> | |||
10 | window.onload = function() { | |||
11 | //Deep link URL for existing users with app already installed on their device | |||
12 | ||||
13 | //<!-- Download URL (MAT link) for new users to download the app --> | |||
14 | // setTimeout("window.location = 'http://hastrk.com/serve?action=click&publisher_id=1&site_id=2';", 1000); | |||
15 | } | |||
16 | </script> | |||
17 | ||||
<script> | 9 | 18 | </head> | |
window.onload = function() { | 10 | 19 | ||
//Deep link URL for existing users with app already installed on their device | 11 | 20 | ||
12 | 21 | <body> | ||
//<!-- Download URL (MAT link) for new users to download the app --> | 13 | 22 | <div class="container"> | |
// setTimeout("window.location = 'http://hastrk.com/serve?action=click&publisher_id=1&site_id=2';", 1000); | 14 | 23 | ||
} | 15 | 24 | <!-- My LOCATION --> | |
</script> | 16 | 25 | <div> | |
17 | 26 | <input type="text" class="form-control text-field" id="origin" placeholder="My Location" onclick="initFrom()"> | ||
</head> | 18 | 27 | </div> | |
19 | 28 | <!-- Destination --> | ||
20 | 29 | <div> | ||
<body> | 21 | 30 | <input type="text" class="form-control text-field" id="dest" placeholder="Final Destination" onclick="initTo()"> | |
<div class="container"> | 22 | 31 | </div> | |
23 | 32 | |||
<!-- My LOCATION --> | 24 | 33 | <!-- Buttons and status information on top of the map. --> | |
<div> | 25 | 34 | <div class="container" id="map-top"> | |
<input type="text" class="form-control text-field" id="origin" placeholder="My Location" onclick="initFrom()"> | 26 | 35 | <div class="row"> | |
</div> | 27 | 36 | <!-- Status --> | |
<!-- Destination --> | 28 | 37 | <div class="col-xs-6"> | |
<div> | 29 | 38 | <!-- Title: Current Route --> | |
<input type="text" class="form-control text-field" id="dest" placeholder="Final Destination" onclick="initTo()"> | 30 | 39 | <div> | |
</div> | 31 | 40 | Current Route Safety: | |
32 | 41 | </div> | ||
<!-- Buttons and status information on top of the map. --> | 33 | 42 | <!-- Icons + Status --> | |
<div class="container" id="map-top"> | 34 | 43 | <div class="row status"> | |
<div class="row"> | 35 | 44 | <!-- Safety Rating --> | |
<!-- Status --> | 36 | 45 | <div class="col-xs-4" name="grade"> | |
<div class="col-xs-6"> | 37 | 46 | ||
<!-- Title: Current Route --> | 38 | 47 | </div> | |
48 | </div> | |||
49 | </div> | |||
50 | ||||
51 | <div class="col-xs-6"> | |||
52 | <div> | |||
53 | Estimated Route Time: | |||
54 | </div> | |||
55 | <div class="row status"> | |||
<div> | 39 | 56 | <!-- Travel Duration --> | |
Current Route Safety: | 40 | 57 | <div class="col-xs-4" name="time"> | |
</div> | 41 | |||
<!-- Icons + Status --> | 42 | 58 | </div> | |
<div class="row status"> | 43 | 59 | </div> | |
<!-- Safety Rating --> | 44 | 60 | </div> | |
<div class="col-xs-4" name="grade"> | 45 | 61 | ||
46 | 62 | |||
</div> | 47 | 63 | <!-- Alt Routes | |
64 | <div class="col-xs-4"> | |||
65 | <button type="button" class="btn btn-default btn-block text-center" id="alt-routes" onclick="todo()">Alternate Routes</button> | |||
</div> | 48 | 66 | </div> | |
67 | --> | |||
</div> | 49 | 68 | </div> | |
50 | 69 | </div> | ||
<div class="col-xs-6"> | 51 | 70 | ||
<div> | 52 | 71 | <!-- Floating panel for key or for toggle heat map | |
Estimated Route Time: | 53 | 72 | <div id="floating-panel"> | |
73 | <button onclick="toggleHeatmap()">Toggle Heatmap</button> | |||
74 | <button onclick="changeGradient()">Change gradient</button> | |||
75 | <button onclick="changeRadius()">Change radius</button> | |||
76 | <button onclick="changeOpacity()">Change opacity</button> | |||
77 | </div> | |||
78 | --> | |||
79 | ||||
80 | <div> Move a circle to get a crime summary of that location (half mile radius)</div> | |||
81 | ||||
82 | <dir class="container" id="map-area"> | |||
83 | <div class="row"> | |||
84 | <!-- Div container for map. --> | |||
85 | <div class="col-xs-7" id="map"></div> | |||
86 | <!-- Div container for the directions display--> | |||
87 | <div class="col-xs-5" id="right-panel"></div> | |||
88 | </div> | |||
89 | </dir> | |||
</div> | 54 | 90 | ||
<div class="row status"> | 55 | 91 | <!-- Javascript for the map. --> | |
<!-- Travel Duration --> | 56 | 92 | <script> | |
<div class="col-xs-4" name="time"> | 57 | 93 | var map, heatmap; | |
</div> | 58 | 94 | // Used by the Google Maps Direction API. | |
</div> | 59 | 95 | var directionsService; | |
</div> | 60 | 96 | var directionsDisplay; | |
97 | //array to hold the circle added to map | |||
98 | var circleArray; | |||
61 | 99 | |||
62 | 100 | function initMap() { | ||
101 | circleArray = []; | |||
<!-- Alt Routes | 63 | 102 | directionsService = new google.maps.DirectionsService; | |
<div class="col-xs-4"> | 64 | 103 | directionsDisplay = new google.maps.DirectionsRenderer; | |
<button type="button" class="btn btn-default btn-block text-center" id="alt-routes" onclick="todo()">Alternate Routes</button> | 65 | 104 | // Setting default map location i.e. Geisel Library. | |
</div> | 66 | 105 | map = new google.maps.Map(document.getElementById('map'), { | |
--> | 67 | 106 | zoom: 13, | |
</div> | 68 | 107 | center: {lat: 37.775, lng: -122.434} | |
</div> | 69 | 108 | }); | |
70 | ||||
<!-- Floating panel for key or for toggle heat map | 71 | |||
<div id="floating-panel"> | 72 | |||
<button onclick="toggleHeatmap()">Toggle Heatmap</button> | 73 | |||
<button onclick="changeGradient()">Change gradient</button> | 74 | |||
<button onclick="changeRadius()">Change radius</button> | 75 | 109 | directionsDisplay.setMap(map); | |
<button onclick="changeOpacity()">Change opacity</button> | 76 | 110 | directionsDisplay.setPanel(document.getElementById('right-panel')); | |
</div> | 77 | 111 | // Keeping the map center when the browser is resized. | |
--> | 78 | 112 | function resizing() { | |
79 | 113 | var center = map.getCenter(); | ||
<div> Move a circle to get a crime summary of that location (half mile radius)</div> | 80 | 114 | google.maps.event.trigger(map, "resize"); | |
81 | 115 | map.setCenter(center); | ||
<dir class="container" id="map-area"> | 82 | 116 | } | |
<div class="row"> | 83 | 117 | ||
<!-- Div container for map. --> | 84 | 118 | google.maps.event.addDomListener(window, "resize", resizing); | |
<div class="col-xs-7" id="map"></div> | 85 | 119 | // Show route. | |
<!-- Div container for the directions display--> | 86 | |||
<div class="col-xs-5" id="right-panel"></div> | 87 | 120 | displayRoute(directionsService, directionsDisplay); | |
</div> | 88 | 121 | } | |
</dir> | 89 | 122 | ||
90 | 123 | function displayRoute(directionsService, directionsDisplay) { | ||
<!-- Javascript for the map. --> | 91 | 124 | // Textfields that show from and to. | |
<script> | 92 | 125 | document.getElementById("origin").value = localStorage.getItem("fromIn"); | |
93 | 126 | document.getElementById("dest").value = localStorage.getItem("toIn"); | ||
var map, heatmap; | 94 | 127 | ||
// Used by the Google Maps Direction API. | 95 | 128 | // Default mode to driving if none was chosen. | |
var directionsService; | 96 | 129 | var mode = localStorage.getItem("mode"); | |
var directionsDisplay; | 97 | 130 | ||
//array to hold the circle added to map | 98 | 131 | if (mode != "WALKING" && mode != "DRIVING" && mode != "TRANSIT") { | |
var circleArray; | 99 | 132 | mode = "DRIVING"; | |
100 | 133 | } | ||
function initMap() { | 101 | 134 | ||
135 | var originPoint = new google.maps.LatLng(localStorage.getItem("originLat"), localStorage.getItem("originLng")); | |||
136 | var destPoint = new google.maps.LatLng(localStorage.getItem("destLat"), localStorage.getItem("destLng")); | |||
137 | ||||
circleArray = []; | 102 | 138 | var request = { | |
directionsService = new google.maps.DirectionsService; | 103 | 139 | origin: originPoint, | |
directionsDisplay = new google.maps.DirectionsRenderer; | 104 | 140 | destination: destPoint, | |
// Setting default map location i.e. Geisel Library. | 105 | 141 | travelMode: mode | |
map = new google.maps.Map(document.getElementById('map'), { | 106 | 142 | }; | |
zoom: 13, | 107 | 143 | ||
center: {lat: 37.775, lng: -122.434} | 108 | 144 | directionsService.route(request, function(response, status) { | |
}); | 109 | 145 | if (status === "OK") { | |
146 | console.log(response); | |||
directionsDisplay.setMap(map); | 110 | 147 | directionsDisplay.setDirections(response); | |
148 | document.getElementsByName("time")[0].innerHTML = response.routes[0].legs[0].duration.text | |||
149 | //Set up the markers to show danger in different areas | |||
150 | getPoints(response.routes[0]); | |||
directionsDisplay.setPanel(document.getElementById('right-panel')); | 111 | 151 | } | |
// Keeping the map center when the browser is resized. | 112 | 152 | else { | |
function resizing() { | 113 | 153 | window.alert("Directions request failed due to " + status); | |
var center = map.getCenter(); | 114 | 154 | } | |
google.maps.event.trigger(map, "resize"); | 115 | 155 | }); | |
map.setCenter(center); | 116 | 156 | } | |
} | 117 | 157 | ||
118 | 158 | function initFrom() { | ||
google.maps.event.addDomListener(window, "resize", resizing); | 119 | 159 | var temp = document.getElementById("origin"); | |
// Show route. | 120 | |||
displayRoute(directionsService, directionsDisplay); | 121 | 160 | var autoOrigin = new google.maps.places.Autocomplete(temp); | |
} | 122 | |||
123 | 161 | google.maps.event.addListener(autoOrigin, 'place_changed', function () { | ||
function displayRoute(directionsService, directionsDisplay) { | 124 | 162 | var place = autoOrigin.getPlace(); | |
// Textfields that show from and to. | 125 | 163 | // Testing. | |
document.getElementById("origin").value = localStorage.getItem("fromIn"); | 126 | 164 | console.log(place); | |
document.getElementById("dest").value = localStorage.getItem("toIn"); | 127 | 165 | localStorage.setItem("fromIn", temp.value); | |
166 | localStorage.setItem("originLat", place.geometry.location.lat()); | |||
167 | localStorage.setItem("originLng", place.geometry.location.lng()); | |||
168 | //Prevent memory leak, remove circle from last route | |||
169 | removeCircles(); | |||
170 | clearGrade(); | |||
128 | 171 | displayRoute(directionsService, directionsDisplay); | ||
// Default mode to driving if none was chosen. | 129 | 172 | }); | |
var mode = localStorage.getItem("mode"); | 130 | 173 | } | |
131 | 174 | |||
if (mode != "WALKING" && mode != "DRIVING" && mode != "TRANSIT") { | 132 | 175 | function initTo() { | |
mode = "DRIVING"; | 133 | 176 | var temp = document.getElementById("dest"); | |
} | 134 | |||
135 | 177 | var autoDest = new google.maps.places.Autocomplete(temp); | ||
var originPoint = new google.maps.LatLng(localStorage.getItem("originLat"), localStorage.getItem("originLng")); | 136 | |||
var destPoint = new google.maps.LatLng(localStorage.getItem("destLat"), localStorage.getItem("destLng")); | 137 | 178 | google.maps.event.addListener(autoDest, 'place_changed', function () { | |
138 | 179 | var place = autoDest.getPlace(); | ||
var request = { | 139 | 180 | // Testing. | |
origin: originPoint, | 140 | 181 | console.log(place); | |
destination: destPoint, | 141 | 182 | localStorage.setItem("toIn", temp.value); | |
183 | localStorage.setItem("destLat", place.geometry.location.lat()); | |||
184 | localStorage.setItem("destLng", place.geometry.location.lng()); | |||
185 | //Prevent memory leak, remove circle from last route | |||
186 | removeCircles(); | |||
187 | clearGrade(); | |||
travelMode: mode | 142 | 188 | displayRoute(directionsService, directionsDisplay); | |
}; | 143 | 189 | }); | |
144 | 190 | } | ||
directionsService.route(request, function(response, status) { | 145 | 191 | ||
if (status === "OK") { | 146 | 192 | function getPoints(route) { | |
console.log(response); | 147 | 193 | //set up HTTP client to make GET request to API | |
directionsDisplay.setDirections(response); | 148 | 194 | var HttpClient = function() { | |
document.getElementsByName("time")[0].innerHTML = response.routes[0].legs[0].duration.text | 149 | 195 | this.get = function(aUrl, aCallback) { | |
196 | var anHttpRequest = new XMLHttpRequest(); | |||
197 | anHttpRequest.onreadystatechange = function() { | |||
198 | if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200) | |||
199 | aCallback(anHttpRequest.responseText); | |||
200 | } | |||
201 | //gj4g6D3zxKmshys7yyf9KNbuywOxp1ueazFjsn6n2fK4sccIGA | |||
202 | anHttpRequest.open( "GET", aUrl, true ); | |||
203 | anHttpRequest.setRequestHeader("X-Mashape-Key", "W4MVkGfi3ZmshbMN0LqORTZunzeTp1rEG5Pjsn9og1t7bRnQxG"); | |||
204 | //W4MVkGfi3ZmshbMN0LqORTZunzeTp1rEG5Pjsn9og1t7bRnQxG | |||
205 | anHttpRequest.setRequestHeader("Accept", "application/json") | |||
206 | anHttpRequest.send( null ); | |||
207 | } | |||
208 | } | |||
//Set up the markers to show danger in different areas | 150 | 209 | ||
getPoints(response.routes[0]); | 151 | 210 | //Loop through the steps of the route to get the center point | |
} | 152 | 211 | var bounds = new google.maps.LatLngBounds(); | |
212 | var legs = route.legs; | |||
213 | for (i=0;i<legs.length;i++) { | |||
214 | var steps = legs[i].steps; | |||
215 | for (j=0;j<steps.length;j++) { | |||
216 | var nextSegment = steps[j].path; | |||
217 | for (k=0;k<nextSegment.length;k++) { | |||
218 | // polyline.getPath().push(nextSegment[k]); | |||
219 | bounds.extend(nextSegment[k]); | |||
220 | } | |||
221 | } | |||
222 | } | |||
else { | 153 | 223 | ||
window.alert("Directions request failed due to " + status); | 154 | 224 | //create instance of HTTP client | |
} | 155 | 225 | aClient = new HttpClient(); | |
}); | 156 | 226 | /*Also very possible to use RouteBoxer as a better solution*/ | |
227 | // for (var i = route.legs[0].steps.length - 1; i >= 0; i--) { | |||
228 | ||||
229 | for (var i = 0; i < 3; i++){ | |||
230 | var long = route.legs[0].steps[i].end_point.lng(); | |||
231 | var lat = route.legs[0].steps[i].end_point.lat(); | |||
232 | if(i == 2){ | |||
233 | long = route.legs[0].steps[route.legs[0].steps.length - 1].end_point.lng(); | |||
234 | lat = route.legs[0].steps[route.legs[0].steps.length - 1].end_point.lat(); | |||
235 | } | |||
236 | ||||
237 | //If middle point then get the center point from bounds | |||
238 | else if(i == 1){ | |||
239 | long = bounds.getCenter().lng(); | |||
240 | lat = bounds.getCenter().lat(); | |||
241 | } | |||
242 | //Base string concatenated with GET request options | |||
243 | var str = "https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=".concat(lat).concat("&lon=").concat(long); | |||
244 | //Make the HTTP request | |||
245 | aClient.get(str, function(result) { | |||
246 | //check what the score is | |||
247 | var object = JSON.parse(result); | |||
248 | var score = parseInt(object.score); | |||
249 | var grade = object.grade; | |||
250 | console.log(result); | |||
251 | ||||
252 | ||||
253 | //Colors: | |||
254 | //Yellow: #f1c40f | |||
255 | //Red: #e74c3c | |||
256 | //Light Green: #2ecc71 | |||
257 | //Dark Green: #27ae60 | |||
258 | //Blue: #3498db | |||
259 | ||||
260 | //create the options object | |||
261 | var circle = new google.maps.Circle({ | |||
262 | strokeColor: '#3498db', | |||
263 | strokeOpacity: 0.8, | |||
264 | strokeWeight: 2, | |||
265 | fillColor: '#3498db', | |||
266 | fillOpacity: 0.35, | |||
267 | map: map, | |||
268 | center: {lat: parseFloat(object.latitude), lng: parseFloat(object.longitude)}, | |||
269 | radius: 804, | |||
270 | draggable: true | |||
271 | }) | |||
272 | ||||
273 | setColor(score, circle); | |||
274 | setGrade(object.grade); | |||
275 | circleArray.push(circle); | |||
276 | circle.addListener('dragend', function(event){ | |||
277 | var newLat = event.latLng.lat(); | |||
278 | var newLong = event.latLng.lng(); | |||
279 | ||||
280 | var newStr = "https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=".concat(newLat).concat("&lon=").concat(newLong) | |||
281 | aClient.get(newStr, function(result){ | |||
282 | //change the color | |||
283 | var newObject = JSON.parse(result); | |||
284 | var newScore = parseInt(newObject.score); | |||
285 | setColor(newScore, circle); | |||
286 | // setGrade(object.grade); | |||
287 | }); | |||
288 | }); | |||
289 | }); | |||
} | 157 | 290 | } | |
291 | } | |||
158 | 292 | |||
function initFrom() { | 159 | 293 | function toggleHeatmap(){ | |
var temp = document.getElementById("origin"); | 160 | 294 | //add the heatmap to the thing | |
var autoOrigin = new google.maps.places.Autocomplete(temp); | 161 | 295 | } | |
google.maps.event.addListener(autoOrigin, 'place_changed', function () { | 162 | 296 | ||
var place = autoOrigin.getPlace(); | 163 | 297 | function setGrade(grade){ | |
// Testing. | 164 | 298 | var currGrade = document.getElementsByName("grade")[0].innerHTML = grade; | |
console.log(place); | 165 | 299 | if(currGrade == "N/A"){ | |
localStorage.setItem("fromIn", temp.value); | 166 | 300 | if(grade != "N/A"){ | |
localStorage.setItem("originLat", place.geometry.location.lat()); | 167 | 301 | document.getElementsByName("grade")[0].innerHTML = grade; | |
localStorage.setItem("originLng", place.geometry.location.lng()); | 168 | 302 | } | |
//Prevent memory leak, remove circle from last route | 169 | 303 | } else { | |
removeCircles(); | 170 | 304 | if(currGrade > grade){ | |
clearGrade(); | 171 | 305 | document.getElementsByName("grade")[0].innerHTML = grade; | |
displayRoute(directionsService, directionsDisplay); | 172 | 306 | } | |
}); | 173 | 307 | } | |
} | 174 | 308 | } | |
175 | 309 | |||
function initTo() { | 176 | 310 | function clearGrade(){ | |
var temp = document.getElementById("dest"); | 177 | 311 | document.getElementsByName("grade")[0].innerHTML = ""; | |
var autoDest = new google.maps.places.Autocomplete(temp); | 178 | 312 | } | |
google.maps.event.addListener(autoDest, 'place_changed', function () { | 179 | 313 | ||
var place = autoDest.getPlace(); | 180 | 314 | function setColor(score, circle){ | |
// Testing. | 181 | 315 | if(score == 0){ | |
console.log(place); | 182 | 316 | //set color to blue | |
localStorage.setItem("toIn", temp.value); | 183 | 317 | circle.setOptions({fillColor: '#3498db', strokeColor: '#3498db' }); | |
localStorage.setItem("destLat", place.geometry.location.lat()); | 184 | 318 | } else if(0 < score && score < 20){ | |
localStorage.setItem("destLng", place.geometry.location.lng()); | 185 | 319 | //set color to red | |
//Prevent memory leak, remove circle from last route | 186 | 320 | circle.setOptions({fillColor: '#e74c3c', strokeColor: '#e74c3c' }); | |
removeCircles(); | 187 | 321 | } else if(20 <= score && score < 40){ | |
clearGrade(); | 188 | 322 | //set orange | |
displayRoute(directionsService, directionsDisplay); | 189 | 323 | circle.setOptions({fillColor: '#e67e22', strokeColor: '#e67e22' }); | |
}); | 190 | 324 | } else if(40 <= score && score < 60){ | |
} | 191 | 325 | //set yellow | |
192 | 326 | circle.setOptions({fillColor: '#f1c40f', strokeColor: '#f1c40f' }); | ||
function getPoints(route) { | 193 | 327 | } else if(60 <= score && score < 80){ | |
//set up HTTP client to make GET request to API | 194 | 328 | //light green | |
var HttpClient = function() { | 195 | 329 | circle.setOptions({fillColor: '#2ecc71', strokeColor: '#2ecc71' }); | |
this.get = function(aUrl, aCallback) { | 196 | 330 | } else { | |
var anHttpRequest = new XMLHttpRequest(); | 197 | 331 | //dark green | |
anHttpRequest.onreadystatechange = function() { | 198 | 332 | circle.setOptions({fillColor: '#27ae60', strokeColor: '#27ae60' }); | |
if (anHttpRequest.readyState == 4 && anHttpRequest.status == 200) | 199 | 333 | } | |
aCallback(anHttpRequest.responseText); | 200 | 334 | } | |
} | 201 | 335 | ||
//gj4g6D3zxKmshys7yyf9KNbuywOxp1ueazFjsn6n2fK4sccIGA | 202 | 336 | function removeCircles(){ | |
anHttpRequest.open( "GET", aUrl, true ); | 203 | 337 | for(var i = 0; i< circleArray.length; i++){ | |
anHttpRequest.setRequestHeader("X-Mashape-Key", "W4MVkGfi3ZmshbMN0LqORTZunzeTp1rEG5Pjsn9og1t7bRnQxG"); | 204 | 338 | circleArray[i].setMap(null); | |
//W4MVkGfi3ZmshbMN0LqORTZunzeTp1rEG5Pjsn9og1t7bRnQxG | 205 | 339 | } | |
anHttpRequest.setRequestHeader("Accept", "application/json") | 206 | 340 | circleArray = []; | |
anHttpRequest.send( null ); | 207 | 341 | } | |
} | 208 | 342 | ||
} | 209 | 343 | function getCircle() { | |
210 | 344 | return { | ||
//Loop through the steps of the route to get the center point | 211 | 345 | path: google.maps.SymbolPath.CIRCLE, | |
var bounds = new google.maps.LatLngBounds(); | 212 | 346 | fillColor: 'red', | |
var legs = route.legs; | 213 | 347 | fillOpacity: .8, | |
for (i=0;i<legs.length;i++) { | 214 | 348 | scale: 10, | |
var steps = legs[i].steps; | 215 | 349 | strokeColor: 'white', | |
for (j=0;j<steps.length;j++) { | 216 | 350 | strokeWeight: .5 | |
var nextSegment = steps[j].path; | 217 | 351 | }; | |
for (k=0;k<nextSegment.length;k++) { | 218 | |||
// polyline.getPath().push(nextSegment[k]); | 219 | |||
bounds.extend(nextSegment[k]); | 220 | |||
} | 221 | |||
} | 222 | |||
} | 223 | |||
224 | ||||
//create instance of HTTP client | 225 | |||
aClient = new HttpClient(); | 226 | |||
/*Also very possible to use RouteBoxer as a better solution*/ | 227 | |||
// for (var i = route.legs[0].steps.length - 1; i >= 0; i--) { | 228 | |||
229 | ||||
for (var i = 0; i < 3; i++){ | 230 | |||
var long = route.legs[0].steps[i].end_point.lng(); | 231 | |||
var lat = route.legs[0].steps[i].end_point.lat(); | 232 | |||
if(i == 2){ | 233 | |||
long = route.legs[0].steps[route.legs[0].steps.length - 1].end_point.lng(); | 234 | |||
lat = route.legs[0].steps[route.legs[0].steps.length - 1].end_point.lat(); | 235 | |||
} | 236 | |||
237 | ||||
//If middle point then get the center point from bounds | 238 | |||
else if(i == 1){ | 239 | |||
long = bounds.getCenter().lng(); | 240 | |||
lat = bounds.getCenter().lat(); | 241 | |||
} | 242 | |||
//Base string concatenated with GET request options | 243 | |||
var str = "https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=".concat(lat).concat("&lon=").concat(long); | 244 | |||
//Make the HTTP request | 245 | |||
aClient.get(str, function(result) { | 246 | |||
//check what the score is | 247 | |||
var object = JSON.parse(result); | 248 | |||
var score = parseInt(object.score); | 249 | |||
var grade = object.grade; | 250 | |||
console.log(result); | 251 | |||
252 | ||||
253 | ||||
//Colors: | 254 | |||
//Yellow: #f1c40f | 255 | |||
//Red: #e74c3c | 256 | |||
//Light Green: #2ecc71 | 257 | |||
//Dark Green: #27ae60 | 258 | |||
//Blue: #3498db | 259 | |||
260 | ||||
//create the options object | 261 | |||
var circle = new google.maps.Circle({ | 262 | |||
strokeColor: '#3498db', | 263 | |||
strokeOpacity: 0.8, | 264 | |||
strokeWeight: 2, | 265 | |||
fillColor: '#3498db', | 266 | |||
fillOpacity: 0.35, | 267 | |||
map: map, | 268 | |||
center: {lat: parseFloat(object.latitude), lng: parseFloat(object.longitude)}, | 269 | |||
radius: 804, | 270 | |||
draggable: true | 271 | |||
}) | 272 | |||
273 | ||||
setColor(score, circle); | 274 | |||
setGrade(object.grade); | 275 | |||
circleArray.push(circle); | 276 | |||
circle.addListener('dragend', function(event){ | 277 | |||
var newLat = event.latLng.lat(); | 278 | |||
var newLong = event.latLng.lng(); | 279 | |||
280 | ||||
var newStr = "https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=".concat(newLat).concat("&lon=").concat(newLong) | 281 | |||
aClient.get(newStr, function(result){ | 282 | |||
//change the color | 283 | |||
var newObject = JSON.parse(result); | 284 | |||
var newScore = parseInt(newObject.score); | 285 | |||
setColor(newScore, circle); | 286 | |||
// setGrade(object.grade); | 287 | |||
}); | 288 | |||
}); | 289 | |||
}); | 290 | |||
} | 291 | |||
} | 292 | |||
293 | ||||
function toggleHeatmap(){ | 294 | |||
//add the heatmap to the thing | 295 | |||
} | 296 | |||
297 | ||||
function setGrade(grade){ | 298 | |||
var currGrade = document.getElementsByName("grade")[0].innerHTML = grade; | 299 | |||
if(currGrade == "N/A"){ | 300 | |||
if(grade != "N/A"){ | 301 | |||
document.getElementsByName("grade")[0].innerHTML = grade; | 302 | |||
} | 303 | |||
} else { | 304 | |||
if(currGrade > grade){ | 305 | |||
document.getElementsByName("grade")[0].innerHTML = grade; | 306 | |||
} | 307 | |||
} | 308 | |||
} | 309 | |||
310 | ||||
function clearGrade(){ | 311 | |||
document.getElementsByName("grade")[0].innerHTML = ""; | 312 | |||
} | 313 | |||
314 | ||||
function setColor(score, circle){ | 315 | |||
if(score == 0){ | 316 | |||
//set color to blue | 317 | |||
circle.setOptions({fillColor: '#3498db', strokeColor: '#3498db' }); | 318 | |||
} else if(0 < score && score < 20){ | 319 | |||
//set color to red | 320 | |||
circle.setOptions({fillColor: '#e74c3c', strokeColor: '#e74c3c' }); | 321 | |||
} else if(20 <= score && score < 40){ | 322 | |||
//set orange | 323 | |||
circle.setOptions({fillColor: '#e67e22', strokeColor: '#e67e22' }); | 324 | |||
} else if(40 <= score && score < 60){ | 325 | |||
//set yellow | 326 | |||
circle.setOptions({fillColor: '#f1c40f', strokeColor: '#f1c40f' }); | 327 | |||
} else if(60 <= score && score < 80){ | 328 | |||
//light green | 329 | |||
circle.setOptions({fillColor: '#2ecc71', strokeColor: '#2ecc71' }); | 330 | |||
} else { | 331 | |||
//dark green | 332 | |||
circle.setOptions({fillColor: '#27ae60', strokeColor: '#27ae60' }); | 333 | |||
} | 334 | |||
} | 335 | |||
336 | ||||
function removeCircles(){ | 337 | |||
for(var i = 0; i< circleArray.length; i++){ | 338 | |||
circleArray[i].setMap(null); | 339 | |||
} | 340 | |||
circleArray = []; | 341 | |||
} | 342 | |||
343 | ||||
function getCircle() { | 344 | |||
return { | 345 | |||
path: google.maps.SymbolPath.CIRCLE, | 346 | |||
fillColor: 'red', | 347 | |||
fillOpacity: .8, | 348 | |||
scale: 10, | 349 | |||
strokeColor: 'white', | 350 | |||
strokeWeight: .5 | 351 | |||
}; | 352 | |||
} | 353 | |||
</script> | 354 | |||
355 | ||||
<!-- Calling google maps API using the function defined above. --> | 356 | |||
<script async defer | 357 | |||
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDAXDHeZXEv4H4ZnThVDxpyAuxVpzOcj_U&callback=initMap&libraries=visualization,places"> | 358 | |||
</script> | 359 | |||
360 | ||||
<!-- Buttons at the bottom of the map --> | 361 | |||
<div class="container"> | 362 | |||
<div class="row"> | 363 | |||
<!-- Emergency --> | 364 | |||
<div class="col-xs-6"> | 365 | |||
<button type="button" class="btn btn-danger btn-block text-center" onclick="emergency()">Emergency</button> | 366 | |||
</div> | 367 | |||
<!-- Open Map --> | 368 | |||
<div class="col-xs-6"> | 369 | |||
<button type="button" class="btn btn-default btn-block text-center" onclick="openInMaps()">Open in Map</button> | 370 | |||
</div> | 371 | |||
</div> | 372 | |||
</div> | 373 | |||
374 | ||||
</div> | 375 | |||
376 | ||||
<script> | 377 | |||
function emergency() { | 378 | |||
var res = confirm("Would you like to call 911?"); | 379 | |||
if( res == true ) { | 380 | |||
alert("calling 911...");// <a href="tel:+1-911">call 911</a> | 381 | |||
} | 382 | |||
else { | 383 | |||
alert("Please stay safe!"); | 384 | |||
} | 385 | |||
} | 386 | |||
387 | ||||
function openInMaps(){ | 388 | |||
389 | ||||
390 | ||||
391 | ||||
if(window.mobilecheck() == true){ | 392 | |||
var str = 'comgooglemaps://?saddr='.concat((document.getElementById('origin').value).concat('&daddr=').concat(document.getElementById('dest').value).concat('&directionsmode').concat(localStorage.getItem('mode'))); | 393 | |||
window.location = str; | 394 | |||
} else { | 395 | |||
alert("This feature only works for mobile devices"); | 396 | |||
} | 397 | |||
398 | ||||
399 | ||||
} | 400 | |||
401 | ||||
function todo() { | 402 | |||
alert("This button is brought to you by the wonderful Wizard of Oz."); | 403 | |||
} | 404 | |||
405 | ||||
406 | ||||
window.mobilecheck = function() { | 407 | |||
var check = false; | 408 | |||
(function(a){if(/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) check = true;})(navigator.userAgent||navigator.vendor||window.opera); | 409 | |||
return check; | 410 | |||
}; | 411 | |||
</script> | 412 | |||
413 | ||||
</body> | 414 | |||
</html> | 415 | |||
416 | ||||
417 | ||||
418 | ||||
419 | ||||
420 | ||||
421 | ||||
422 | ||||
423 | ||||
424 | ||||
425 | ||||
426 | ||||
427 | ||||
428 | ||||
429 | ||||
430 | ||||
431 | ||||
432 | ||||
433 | ||||
434 | ||||
435 | ||||
436 | ||||
437 | ||||
438 | ||||
439 | ||||
440 | ||||
441 | ||||
442 | ||||
443 | ||||
444 | ||||
445 | ||||
446 | ||||
447 | ||||
448 | ||||
449 | ||||
450 | ||||
451 | ||||
452 | ||||
453 | ||||
454 | ||||
455 | ||||
456 | ||||
457 | ||||
458 | ||||
459 | ||||
460 | ||||
461 | ||||
462 | ||||
463 | ||||
464 | ||||
465 | ||||
466 | ||||
467 | ||||
468 | ||||
469 | ||||
470 | ||||
471 |
views/pages/login.ejs
View file @
77ade48
<!DOCTYPE html> | 1 | 1 | <!DOCTYPE html> | |
<html lang="en"> | 2 | 2 | <html lang="en"> | |
<head> | 3 | 3 | <head> | |
<% include ../partials/header %> | 4 | 4 | <% include ../partials/header %> | |
</head> | 5 | 5 | </head> | |
6 | 6 | |||
<!-- Homepage --> | 7 | 7 | <!-- Homepage --> | |
8 | 8 | |||
<body> | 9 | 9 | <body> | |
<div class="container-fluid"> | 10 | 10 | <div class="container-fluid"> | |
<form action="/"> | 11 | 11 | <form action="/"> | |
<div class="form-group"> | 12 | 12 | <div class="form-group"> | |
<input type="hidden" name="loggedin" value="true"/> | 13 | 13 | <input type="hidden" name="loggedin" value="true"/> | |
<input type="currentLocation" class="form-control" id="origin" placeholder="Username" onchange="saveFrom()"> | 14 | 14 | <input type="currentLocation" class="form-control" id="origin" placeholder="Username" onchange="saveFrom()"> | |
<input type="currentLocation" class="form-control" id="origin" placeholder="Password" onchange="saveFrom()"> | 15 | 15 | <input type="currentLocation" class="form-control" id="origin" placeholder="Password" onchange="saveFrom()"> | |
</div> | 16 | 16 | </div> | |
<button class="btn btn-primary">Log In</button> | 17 | 17 | <button class="btn btn-primary">Log In</button> | |
</form> | 18 | 18 | </form> | |
</div> | 19 | 19 | </div> | |
20 | 20 | |||
<!-- Google Map functionality. --> | 21 | 21 | <!-- Google Map functionality. --> | |
<script> | 22 | 22 | <script> | |
</script> | 23 | 23 | </script> | |
24 | 24 | |||
</body> | 25 | 25 | </body> | |
</html> | 26 | 26 | </html> | |