Compare View

switch
from
...
to
 
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>