From ef269e4eef3e14f0ea772702e015a995e44293da Mon Sep 17 00:00:00 2001 From: Paul Wallace Date: Thu, 10 Nov 2016 17:29:51 -0800 Subject: [PATCH] Update to safe03 --- public/scripts/grade.js | 11 + public/scripts/routeboxer.js | 582 ++++++++++++++++++++++++++ public/stylesheets/main.css | 44 +- views/pages/index.ejs | 75 +++- views/pages/landing.ejs | 945 ++++++++++++++++--------------------------- views/pages/login.ejs | 12 +- 6 files changed, 1050 insertions(+), 619 deletions(-) create mode 100644 public/scripts/grade.js create mode 100644 public/scripts/routeboxer.js diff --git a/public/scripts/grade.js b/public/scripts/grade.js new file mode 100644 index 0000000..4c62522 --- /dev/null +++ b/public/scripts/grade.js @@ -0,0 +1,11 @@ +var unirest = require('unirest'); + +function getGrade(long, lat){ + unirest.get("https://crimescore.p.mashape.com/crimescore?f=json&id=174&lat=37.782551&lon=-122.445368") + .header("X-Mashape-Key", "gj4g6D3zxKmshys7yyf9KNbuywOxp1ueazFjsn6n2fK4sccIGA") + .header("Accept", "application/json") + .end(function (result) { + res.render('pages/tempcrime', result); + console.log(result.status, result.headers, result.body); + }); +} \ No newline at end of file diff --git a/public/scripts/routeboxer.js b/public/scripts/routeboxer.js new file mode 100644 index 0000000..1c23b7d --- /dev/null +++ b/public/scripts/routeboxer.js @@ -0,0 +1,582 @@ +/** + * @name RouteBoxer + * @version 1.0 + * @copyright (c) 2010 Google Inc. + * @author Thor Mitchell + * + * @fileoverview The RouteBoxer class takes a path, such as the Polyline for a + * route generated by a Directions request, and generates a set of LatLngBounds + * objects that are guaranteed to contain every point within a given distance + * of that route. These LatLngBounds objects can then be used to generate + * requests to spatial search services that support bounds filtering (such as + * the Google Maps Data API) in order to implement search along a route. + *

+ * RouteBoxer overlays a grid of the specified size on the route, identifies + * every grid cell that the route passes through, and generates a set of bounds + * that cover all of these cells, and their nearest neighbours. Consequently + * the bounds returned will extend up to ~3x the specified distance from the + * route in places. + */ + +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Creates a new RouteBoxer + * + * @constructor + */ +function RouteBoxer() { + this.R = 6371; // earth's mean radius in km + this.initGoogle(); +} + +/** + * Generates boxes for a given route and distance + * + * @param {google.maps.LatLng[] | google.maps.Polyline} path The path along + * which to create boxes. The path object can be either an Array of + * google.maps.LatLng objects or a Maps API v2 or Maps API v3 + * google.maps.Polyline object. + * @param {Number} range The distance in kms around the route that the generated + * boxes must cover. + * @return {google.maps.LatLngBounds[]} An array of boxes that covers the whole + * path. + */ +RouteBoxer.prototype.box = function (path, range) { + // Two dimensional array representing the cells in the grid overlaid on the path + this.grid_ = null; + + // Array that holds the latitude coordinate of each vertical grid line + this.latGrid_ = []; + + // Array that holds the longitude coordinate of each horizontal grid line + this.lngGrid_ = []; + + // Array of bounds that cover the whole route formed by merging cells that + // the route intersects first horizontally, and then vertically + this.boxesX_ = []; + + // Array of bounds that cover the whole route formed by merging cells that + // the route intersects first vertically, and then horizontally + this.boxesY_ = []; + + // The array of LatLngs representing the vertices of the path + var vertices = null; + + // If necessary convert the path into an array of LatLng objects + if (path instanceof Array) { + // already an arry of LatLngs (eg. v3 overview_path) + vertices = path; + } else if (path instanceof google.maps.Polyline) { + if (path.getPath) { + // v3 Maps API Polyline object + vertices = new Array(path.getPath().getLength()); + for (var i = 0; i < vertices.length; i++) { + vertices[i] = path.getPath().getAt(i); + } + } else { + // v2 Maps API Polyline object + vertices = new Array(path.getVertexCount()); + for (var j = 0; j < vertices.length; j++) { + vertices[j] = path.getVertex(j); + } + } + } + + // Build the grid that is overlaid on the route + this.buildGrid_(vertices, range); + + // Identify the grid cells that the route intersects + this.findIntersectingCells_(vertices); + + // Merge adjacent intersected grid cells (and their neighbours) into two sets + // of bounds, both of which cover them completely + this.mergeIntersectingCells_(); + + // Return the set of merged bounds that has the fewest elements + return (this.boxesX_.length <= this.boxesY_.length ? + this.boxesX_ : + this.boxesY_); +}; + +/** + * Generates boxes for a given route and distance + * + * @param {LatLng[]} vertices The vertices of the path over which to lay the grid + * @param {Number} range The spacing of the grid cells. + */ +RouteBoxer.prototype.buildGrid_ = function (vertices, range) { + + // Create a LatLngBounds object that contains the whole path + var routeBounds = new google.maps.LatLngBounds(); + for (var i = 0; i < vertices.length; i++) { + routeBounds.extend(vertices[i]); + } + + // Find the center of the bounding box of the path + var routeBoundsCenter = routeBounds.getCenter(); + + // Starting from the center define grid lines outwards vertically until they + // extend beyond the edge of the bounding box by more than one cell + this.latGrid_.push(routeBoundsCenter.lat()); + + // Add lines from the center out to the north + this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range).lat()); + for (i = 2; this.latGrid_[i - 2] < routeBounds.getNorthEast().lat(); i++) { + this.latGrid_.push(routeBoundsCenter.rhumbDestinationPoint(0, range * i).lat()); + } + + // Add lines from the center out to the south + for (i = 1; this.latGrid_[1] > routeBounds.getSouthWest().lat(); i++) { + this.latGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(180, range * i).lat()); + } + + // Starting from the center define grid lines outwards horizontally until they + // extend beyond the edge of the bounding box by more than one cell + this.lngGrid_.push(routeBoundsCenter.lng()); + + // Add lines from the center out to the east + this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range).lng()); + for (i = 2; this.lngGrid_[i - 2] < routeBounds.getNorthEast().lng(); i++) { + this.lngGrid_.push(routeBoundsCenter.rhumbDestinationPoint(90, range * i).lng()); + } + + // Add lines from the center out to the west + for (i = 1; this.lngGrid_[1] > routeBounds.getSouthWest().lng(); i++) { + this.lngGrid_.unshift(routeBoundsCenter.rhumbDestinationPoint(270, range * i).lng()); + } + + // Create a two dimensional array representing this grid + this.grid_ = new Array(this.lngGrid_.length); + for (i = 0; i < this.grid_.length; i++) { + this.grid_[i] = new Array(this.latGrid_.length); + } +}; + +/** + * Find all of the cells in the overlaid grid that the path intersects + * + * @param {LatLng[]} vertices The vertices of the path + */ +RouteBoxer.prototype.findIntersectingCells_ = function (vertices) { + // Find the cell where the path begins + var hintXY = this.getCellCoords_(vertices[0]); + + // Mark that cell and it's neighbours for inclusion in the boxes + this.markCell_(hintXY); + + // Work through each vertex on the path identifying which grid cell it is in + for (var i = 1; i < vertices.length; i++) { + // Use the known cell of the previous vertex to help find the cell of this vertex + var gridXY = this.getGridCoordsFromHint_(vertices[i], vertices[i - 1], hintXY); + + if (gridXY[0] === hintXY[0] && gridXY[1] === hintXY[1]) { + // This vertex is in the same cell as the previous vertex + // The cell will already have been marked for inclusion in the boxes + continue; + + } else if ((Math.abs(hintXY[0] - gridXY[0]) === 1 && hintXY[1] === gridXY[1]) || + (hintXY[0] === gridXY[0] && Math.abs(hintXY[1] - gridXY[1]) === 1)) { + // This vertex is in a cell that shares an edge with the previous cell + // Mark this cell and it's neighbours for inclusion in the boxes + this.markCell_(gridXY); + + } else { + // This vertex is in a cell that does not share an edge with the previous + // cell. This means that the path passes through other cells between + // this vertex and the previous vertex, and we must determine which cells + // it passes through + this.getGridIntersects_(vertices[i - 1], vertices[i], hintXY, gridXY); + } + + // Use this cell to find and compare with the next one + hintXY = gridXY; + } +}; + +/** + * Find the cell a path vertex is in by brute force iteration over the grid + * + * @param {LatLng[]} latlng The latlng of the vertex + * @return {Number[][]} The cell coordinates of this vertex in the grid + */ +RouteBoxer.prototype.getCellCoords_ = function (latlng) { + for (var x = 0; this.lngGrid_[x] < latlng.lng(); x++) {} + for (var y = 0; this.latGrid_[y] < latlng.lat(); y++) {} + return ([x - 1, y - 1]); +}; + +/** + * Find the cell a path vertex is in based on the known location of a nearby + * vertex. This saves searching the whole grid when working through vertices + * on the polyline that are likely to be in close proximity to each other. + * + * @param {LatLng[]} latlng The latlng of the vertex to locate in the grid + * @param {LatLng[]} hintlatlng The latlng of the vertex with a known location + * @param {Number[]} hint The cell containing the vertex with a known location + * @return {Number[]} The cell coordinates of the vertex to locate in the grid + */ +RouteBoxer.prototype.getGridCoordsFromHint_ = function (latlng, hintlatlng, hint) { + var x, y; + if (latlng.lng() > hintlatlng.lng()) { + for (x = hint[0]; this.lngGrid_[x + 1] < latlng.lng(); x++) {} + } else { + for (x = hint[0]; this.lngGrid_[x] > latlng.lng(); x--) {} + } + + if (latlng.lat() > hintlatlng.lat()) { + for (y = hint[1]; this.latGrid_[y + 1] < latlng.lat(); y++) {} + } else { + for (y = hint[1]; this.latGrid_[y] > latlng.lat(); y--) {} + } + + return ([x, y]); +}; + + +/** + * Identify the grid squares that a path segment between two vertices + * intersects with by: + * 1. Finding the bearing between the start and end of the segment + * 2. Using the delta between the lat of the start and the lat of each + * latGrid boundary to find the distance to each latGrid boundary + * 3. Finding the lng of the intersection of the line with each latGrid + * boundary using the distance to the intersection and bearing of the line + * 4. Determining the x-coord on the grid of the point of intersection + * 5. Filling in all squares between the x-coord of the previous intersection + * (or start) and the current one (or end) at the current y coordinate, + * which is known for the grid line being intersected + * + * @param {LatLng} start The latlng of the vertex at the start of the segment + * @param {LatLng} end The latlng of the vertex at the end of the segment + * @param {Number[]} startXY The cell containing the start vertex + * @param {Number[]} endXY The cell containing the vend vertex + */ +RouteBoxer.prototype.getGridIntersects_ = function (start, end, startXY, endXY) { + var edgePoint, edgeXY, i; + var brng = start.rhumbBearingTo(end); // Step 1. + + var hint = start; + var hintXY = startXY; + + // Handle a line segment that travels south first + if (end.lat() > start.lat()) { + // Iterate over the east to west grid lines between the start and end cells + for (i = startXY[1] + 1; i <= endXY[1]; i++) { + // Find the latlng of the point where the path segment intersects with + // this grid line (Step 2 & 3) + edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); + + // Find the cell containing this intersect point (Step 4) + edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); + + // Mark every cell the path has crossed between this grid and the start, + // or the previous east to west grid line it crossed (Step 5) + this.fillInGridSquares_(hintXY[0], edgeXY[0], i - 1); + + // Use the point where it crossed this grid line as the reference for the + // next iteration + hint = edgePoint; + hintXY = edgeXY; + } + + // Mark every cell the path has crossed between the last east to west grid + // line it crossed and the end (Step 5) + this.fillInGridSquares_(hintXY[0], endXY[0], i - 1); + + } else { + // Iterate over the east to west grid lines between the start and end cells + for (i = startXY[1]; i > endXY[1]; i--) { + // Find the latlng of the point where the path segment intersects with + // this grid line (Step 2 & 3) + edgePoint = this.getGridIntersect_(start, brng, this.latGrid_[i]); + + // Find the cell containing this intersect point (Step 4) + edgeXY = this.getGridCoordsFromHint_(edgePoint, hint, hintXY); + + // Mark every cell the path has crossed between this grid and the start, + // or the previous east to west grid line it crossed (Step 5) + this.fillInGridSquares_(hintXY[0], edgeXY[0], i); + + // Use the point where it crossed this grid line as the reference for the + // next iteration + hint = edgePoint; + hintXY = edgeXY; + } + + // Mark every cell the path has crossed between the last east to west grid + // line it crossed and the end (Step 5) + this.fillInGridSquares_(hintXY[0], endXY[0], i); + + } +}; + +/** + * Find the latlng at which a path segment intersects with a given + * line of latitude + * + * @param {LatLng} start The vertex at the start of the path segment + * @param {Number} brng The bearing of the line from start to end + * @param {Number} gridLineLat The latitude of the grid line being intersected + * @return {LatLng} The latlng of the point where the path segment intersects + * the grid line + */ +RouteBoxer.prototype.getGridIntersect_ = function (start, brng, gridLineLat) { + var d = this.R * ((gridLineLat.toRad() - start.lat().toRad()) / Math.cos(brng.toRad())); + return start.rhumbDestinationPoint(brng, d); +}; + +/** + * Mark all cells in a given row of the grid that lie between two columns + * for inclusion in the boxes + * + * @param {Number} startx The first column to include + * @param {Number} endx The last column to include + * @param {Number} y The row of the cells to include + */ +RouteBoxer.prototype.fillInGridSquares_ = function (startx, endx, y) { + var x; + if (startx < endx) { + for (x = startx; x <= endx; x++) { + this.markCell_([x, y]); + } + } else { + for (x = startx; x >= endx; x--) { + this.markCell_([x, y]); + } + } +}; + +/** + * Mark a cell and the 8 immediate neighbours for inclusion in the boxes + * + * @param {Number[]} square The cell to mark + */ +RouteBoxer.prototype.markCell_ = function (cell) { + var x = cell[0]; + var y = cell[1]; + this.grid_[x - 1][y - 1] = 1; + this.grid_[x][y - 1] = 1; + this.grid_[x + 1][y - 1] = 1; + this.grid_[x - 1][y] = 1; + this.grid_[x][y] = 1; + this.grid_[x + 1][y] = 1; + this.grid_[x - 1][y + 1] = 1; + this.grid_[x][y + 1] = 1; + this.grid_[x + 1][y + 1] = 1; +}; + +/** + * Create two sets of bounding boxes, both of which cover all of the cells that + * have been marked for inclusion. + * + * The first set is created by combining adjacent cells in the same column into + * a set of vertical rectangular boxes, and then combining boxes of the same + * height that are adjacent horizontally. + * + * The second set is created by combining adjacent cells in the same row into + * a set of horizontal rectangular boxes, and then combining boxes of the same + * width that are adjacent vertically. + * + */ +RouteBoxer.prototype.mergeIntersectingCells_ = function () { + var x, y, box; + + // The box we are currently expanding with new cells + var currentBox = null; + + // Traverse the grid a row at a time + for (y = 0; y < this.grid_[0].length; y++) { + for (x = 0; x < this.grid_.length; x++) { + + if (this.grid_[x][y]) { + // This cell is marked for inclusion. If the previous cell in this + // row was also marked for inclusion, merge this cell into it's box. + // Otherwise start a new box. + box = this.getCellBounds_([x, y]); + if (currentBox) { + currentBox.extend(box.getNorthEast()); + } else { + currentBox = box; + } + + } else { + // This cell is not marked for inclusion. If the previous cell was + // marked for inclusion, merge it's box with a box that spans the same + // columns from the row below if possible. + this.mergeBoxesY_(currentBox); + currentBox = null; + } + } + // If the last cell was marked for inclusion, merge it's box with a matching + // box from the row below if possible. + this.mergeBoxesY_(currentBox); + currentBox = null; + } + + // Traverse the grid a column at a time + for (x = 0; x < this.grid_.length; x++) { + for (y = 0; y < this.grid_[0].length; y++) { + if (this.grid_[x][y]) { + + // This cell is marked for inclusion. If the previous cell in this + // column was also marked for inclusion, merge this cell into it's box. + // Otherwise start a new box. + if (currentBox) { + box = this.getCellBounds_([x, y]); + currentBox.extend(box.getNorthEast()); + } else { + currentBox = this.getCellBounds_([x, y]); + } + + } else { + // This cell is not marked for inclusion. If the previous cell was + // marked for inclusion, merge it's box with a box that spans the same + // rows from the column to the left if possible. + this.mergeBoxesX_(currentBox); + currentBox = null; + + } + } + // If the last cell was marked for inclusion, merge it's box with a matching + // box from the column to the left if possible. + this.mergeBoxesX_(currentBox); + currentBox = null; + } +}; + +/** + * Search for an existing box in an adjacent row to the given box that spans the + * same set of columns and if one is found merge the given box into it. If one + * is not found, append this box to the list of existing boxes. + * + * @param {LatLngBounds} The box to merge + */ +RouteBoxer.prototype.mergeBoxesX_ = function (box) { + if (box !== null) { + for (var i = 0; i < this.boxesX_.length; i++) { + if (this.boxesX_[i].getNorthEast().lng() === box.getSouthWest().lng() && + this.boxesX_[i].getSouthWest().lat() === box.getSouthWest().lat() && + this.boxesX_[i].getNorthEast().lat() === box.getNorthEast().lat()) { + this.boxesX_[i].extend(box.getNorthEast()); + return; + } + } + this.boxesX_.push(box); + } +}; + +/** + * Search for an existing box in an adjacent column to the given box that spans + * the same set of rows and if one is found merge the given box into it. If one + * is not found, append this box to the list of existing boxes. + * + * @param {LatLngBounds} The box to merge + */ +RouteBoxer.prototype.mergeBoxesY_ = function (box) { + if (box !== null) { + for (var i = 0; i < this.boxesY_.length; i++) { + if (this.boxesY_[i].getNorthEast().lat() === box.getSouthWest().lat() && + this.boxesY_[i].getSouthWest().lng() === box.getSouthWest().lng() && + this.boxesY_[i].getNorthEast().lng() === box.getNorthEast().lng()) { + this.boxesY_[i].extend(box.getNorthEast()); + return; + } + } + this.boxesY_.push(box); + } +}; + +/** + * Obtain the LatLng of the origin of a cell on the grid + * + * @param {Number[]} cell The cell to lookup. + * @return {LatLng} The latlng of the origin of the cell. + */ +RouteBoxer.prototype.getCellBounds_ = function (cell) { + return new google.maps.LatLngBounds( + new google.maps.LatLng(this.latGrid_[cell[1]], this.lngGrid_[cell[0]]), + new google.maps.LatLng(this.latGrid_[cell[1] + 1], this.lngGrid_[cell[0] + 1])); +}; + + +RouteBoxer.prototype.initGoogle = function() { + /* Based on the Latitude/longitude spherical geodesy formulae & scripts + at http://www.movable-type.co.uk/scripts/latlong.html + (c) Chris Veness 2002-2010 + */ + google.maps.LatLng.prototype.rhumbDestinationPoint = function (brng, dist) { + var R = 6371; // earth's mean radius in km + var d = parseFloat(dist) / R; // d = angular distance covered on earth's surface + var lat1 = this.lat().toRad(), lon1 = this.lng().toRad(); + brng = brng.toRad(); + + var lat2 = lat1 + d * Math.cos(brng); + var dLat = lat2 - lat1; + var dPhi = Math.log(Math.tan(lat2 / 2 + Math.PI / 4) / Math.tan(lat1 / 2 + Math.PI / 4)); + var q = (Math.abs(dLat) > 1e-10) ? dLat / dPhi : Math.cos(lat1); + var dLon = d * Math.sin(brng) / q; + // check for going past the pole + if (Math.abs(lat2) > Math.PI / 2) { + lat2 = lat2 > 0 ? Math.PI - lat2 : - (Math.PI - lat2); + } + var lon2 = (lon1 + dLon + Math.PI) % (2 * Math.PI) - Math.PI; + + if (isNaN(lat2) || isNaN(lon2)) { + return null; + } + return new google.maps.LatLng(lat2.toDeg(), lon2.toDeg()); + }; + + google.maps.LatLng.prototype.rhumbBearingTo = function (dest) { + var dLon = (dest.lng() - this.lng()).toRad(); + var dPhi = Math.log(Math.tan(dest.lat().toRad() / 2 + Math.PI / 4) / Math.tan(this.lat().toRad() / 2 + Math.PI / 4)); + if (Math.abs(dLon) > Math.PI) { + dLon = dLon > 0 ? -(2 * Math.PI - dLon) : (2 * Math.PI + dLon); + } + return Math.atan2(dLon, dPhi).toBrng(); + }; + +}; + +/** + * Extend the Number object to convert degrees to radians + * + * @return {Number} Bearing in radians + * @ignore + */ +Number.prototype.toRad = function () { + return this * Math.PI / 180; +}; + +/** + * Extend the Number object to convert radians to degrees + * + * @return {Number} Bearing in degrees + * @ignore + */ +Number.prototype.toDeg = function () { + return this * 180 / Math.PI; +}; + +/** + * Normalize a heading in degrees to between 0 and +360 + * + * @return {Number} Return + * @ignore + */ +Number.prototype.toBrng = function () { + return (this.toDeg() + 360) % 360; +}; \ No newline at end of file diff --git a/public/stylesheets/main.css b/public/stylesheets/main.css index 6034655..546fd78 100644 --- a/public/stylesheets/main.css +++ b/public/stylesheets/main.css @@ -1,10 +1,18 @@ +html, body { + height: 100%; + width: 100%; +} + #alt-routes { height: 75px; } #map { height: 600px; - margin-bottom: 10px; +} + +#map-area { + height: 600px; width: 100%; } @@ -12,6 +20,13 @@ margin-bottom: 30px; } +#right-panel { + height: 600px; + /*float: right; + width: 40%;*/ + overflow: auto; +} + .img-responsive { margin: 0 auto; } @@ -37,4 +52,29 @@ .log-in-textfield{ width: 30%; -} \ No newline at end of file +} + +#floating-panel { + position: absolute; + top: 10px; + left: 25%; + z-index: 5; + background-color: #fff; + padding: 5px; + border: 1px solid #999; + text-align: center; + font-family: 'Roboto','sans-serif'; + line-height: 30px; + padding-left: 10px; +} +#floating-panel { + background-color: #fff; + border: 1px solid #999; + left: 25%; + padding: 5px; + position: absolute; + top: 10px; + z-index: 5; +} +/** +*/ \ No newline at end of file diff --git a/views/pages/index.ejs b/views/pages/index.ejs index 4bf4838..27bed3d 100644 --- a/views/pages/index.ejs +++ b/views/pages/index.ejs @@ -13,6 +13,9 @@

The best way to find the safest routes.

+ + + <% if(!locals.loggedin || (locals.loggedin != "true")){ %> <%console.log(locals.loggedin)%> @@ -22,6 +25,7 @@ <% } %> + @@ -30,13 +34,13 @@
- - + +
- - + +
+ + \ No newline at end of file diff --git a/views/pages/landing.ejs b/views/pages/landing.ejs index eebd31e..4add274 100644 --- a/views/pages/landing.ejs +++ b/views/pages/landing.ejs @@ -6,6 +6,15 @@ src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDAXDHeZXEv4H4ZnThVDxpyAuxVpzOcj_U&libraries=visualization"> --> + + @@ -14,11 +23,11 @@
- +
- +
@@ -28,655 +37,379 @@
- Current Route: + Current Route Safety:
-
- 80% Safe +
+
+
+
+ +
+
+ Estimated Route Time: +
+
-
- 10 Mins +
- -
- + + +
- -
+ + +
Move a circle to get a crime summary of that location (half mile radius)
+ + +
+ +
+ +
+
+
-
- -
- -
- Current Area: -
- -
- 80% Safe -
-
-
- +
+
-
- +
+
- + \ No newline at end of file diff --git a/views/pages/login.ejs b/views/pages/login.ejs index 8dc2579..a6b87bd 100644 --- a/views/pages/login.ejs +++ b/views/pages/login.ejs @@ -8,13 +8,13 @@
- -
+ +
- - -
- + + +
+
-- 1.9.1