Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

search section is now refined #38

Merged
merged 20 commits into from
Apr 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 56 additions & 4 deletions flask-backend/src/flask_server.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from functools import cmp_to_key
from flask import Flask, request, Response
from flask_cors import CORS
import jsonpickle
import os
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta

import logging
logging.basicConfig(level=logging.DEBUG)
Expand Down Expand Up @@ -49,7 +53,7 @@ def health():
'data' : True
}
except Exception as error:
response = {
response = {
'error' : error
}
status = 500
Expand All @@ -71,6 +75,54 @@ def multiply(x, y):
response_pickled = jsonpickle.encode(response)
return Response(response=response_pickled, status=status, mimetype='application/json')

@app.route('/api/crime_freq', methods=["GET"])
def get_crime_freq():
status = 200
try:
cursor = mydb.cursor()
timeToFilter = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) - relativedelta(days=29)
reportedDateFilter = int(time.mktime(timeToFilter.timetuple()) * 1000)
cursor.execute(f"select floor((REPORTED_DATE - {reportedDateFilter}) / 86400000) as day, count(*) as crimes_reported from crime group by 1")
raw_data = cursor.fetchall()
row_headers = [x[0] for x in cursor.description]
print(row_headers)
json_data = []

for r in raw_data:
if(int(r[0]) < 0):
continue
json_data.append({
'day': int(r[0]),
'crimes': r[1]
})

added_json_data = []
for i in range(0,30):
found = False
for r in json_data:
if i == r['day']:
found = True
break
if not found:
added_json_data.append({
'day': i,
'crimes': 0
})
json_data = json_data + added_json_data
json_data = sorted(json_data, key=cmp_to_key(lambda x1, x2: x1['day'] - x2['day']))
response = {
'data' : json_data
}

print(response)
except Exception as error:
response = {
'error' : error
}
status = 500
response_pickled = jsonpickle.encode(response)
return Response(response=response_pickled, status=status, mimetype='application/json')

@app.route('/api/crime_totals', methods=["GET"])
def get_crime_totals():
status = 200
Expand Down Expand Up @@ -101,7 +153,6 @@ def get_crime_totals():
status = 500
response_pickled = jsonpickle.encode(response)
return Response(response=response_pickled, status=status, mimetype='application/json')


@app.route('/api/alldata', methods=["GET"])
def get_all():
Expand All @@ -119,8 +170,9 @@ def get_all():
endTime = int(request.args["endTime"])
print("reached")
cursor = mydb.cursor()
print(f"SELECT * FROM crime WHERE FIRST_OCCURRENCE_DATE < {endTime} AND FIRST_OCCURRENCE_DATE > {startTime} ORDER BY (POWER(GEO_LAT-{lat}, 2)+POWER(GEO_LON-{long}, 2)) LIMIT {pagesize} OFFSET {(pageno-1)*pagesize}")
cursor.execute(f"SELECT * FROM crime WHERE FIRST_OCCURRENCE_DATE < {endTime} AND FIRST_OCCURRENCE_DATE > {startTime} ORDER BY (POWER(GEO_LAT-{lat}, 2)+POWER(GEO_LON-{long}, 2)) LIMIT {pagesize} OFFSET {(pageno-1)*pagesize}")
queryToExecute = f"SELECT * FROM crime WHERE REPORTED_DATE < {endTime} AND REPORTED_DATE > {startTime} ORDER BY (POWER(GEO_LAT-{lat}, 2)+POWER(GEO_LON-{long}, 2)) LIMIT {pagesize} OFFSET {(pageno-1)*pagesize}"
print(queryToExecute)
cursor.execute(queryToExecute)
raw_data = cursor.fetchall()
row_headers = [x[0] for x in cursor.description]
print(row_headers)
Expand Down
19 changes: 15 additions & 4 deletions flask-data-gatherer/src/flask_data_gatherer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import requests
import jsonpickle
import os
import time
from datetime import datetime
from dateutil.relativedelta import relativedelta

import logging
logging.basicConfig(level=logging.DEBUG)
Expand Down Expand Up @@ -39,9 +42,10 @@ def db_setup():

if (len(tables) == 0 or 'crime' not in tables[0]):
app.logger.info("Recreate crime table")
table_creation_script = "CREATE TABLE IF NOT EXISTS `crime` (OBJECTID int NOT NULL UNIQUE, INCIDENT_ID double, OFFENSE_ID varchar(20), OFFENSE_CODE varchar(4), OFFENSE_CODE_EXTENSION int, OFFENSE_TYPE_ID varchar(30), OFFENSE_CATEGORY_ID varchar(30), FIRST_OCCURRENCE_DATE long, LAST_OCCURRENCE_DATE long, REPORTED_DATE long, INCIDENT_ADDRESS varchar(100), GEO_X double, GEO_Y double, GEO_LON double, GEO_LAT double, DISTRICT_ID varchar(3), PRECINCT_ID varchar(4), NEIGHBORHOOD_ID varchar(100), IS_CRIME int, IS_TRAFFIC int, VICTIM_COUNT double, PRIMARY KEY (`OBJECTID`));"
table_creation_script = "CREATE TABLE IF NOT EXISTS `crime` (OBJECTID int NOT NULL UNIQUE, INCIDENT_ID double, OFFENSE_ID varchar(20), OFFENSE_CODE varchar(4), OFFENSE_CODE_EXTENSION int, OFFENSE_TYPE_ID varchar(30), OFFENSE_CATEGORY_ID varchar(30), FIRST_OCCURRENCE_DATE long, LAST_OCCURRENCE_DATE long, REPORTED_DATE BIGINT NOT NULL, INCIDENT_ADDRESS varchar(100), GEO_X double, GEO_Y double, GEO_LON double, GEO_LAT double, DISTRICT_ID varchar(3), PRECINCT_ID varchar(4), NEIGHBORHOOD_ID varchar(100), IS_CRIME int, IS_TRAFFIC int, VICTIM_COUNT double, PRIMARY KEY (`OBJECTID`));"
cursor.execute(table_creation_script)

cursor.execute("DELETE FROM crime")
cursor.execute('SELECT COUNT(*) FROM crime')
count = cursor.fetchall()
if (count[0][0] == 0):
Expand All @@ -50,16 +54,23 @@ def db_setup():
app.logger.info("Pull Data from API")

# TODO: decide the minimum number of rows we want in the table. Minimized due to space in Cloud SQL server
init_num = 10
init_num = 10000

newest = f"https://services1.arcgis.com/zdB7qR0BtYrg0Xpl/ArcGIS/rest/services/ODC_CRIME_OFFENSES_P/FeatureServer/324/query?where=OBJECTID%3E%3D0&objectIds=&time=&geometry=&geometryType=esriGeometryEnvelope&inSR=&spatialRel=esriSpatialRelIntersects&resultType=standard&distance=0.0&units=esriSRUnit_Meter&relationParam=&returnGeodetic=false&outFields=OBJECTID%2C+INCIDENT_ID%2C+OFFENSE_ID%2C+OFFENSE_CODE%2C+OFFENSE_CODE_EXTENSION%2C+OFFENSE_TYPE_ID%2C+OFFENSE_CATEGORY_ID%2C+FIRST_OCCURRENCE_DATE%2C+LAST_OCCURRENCE_DATE%2C+REPORTED_DATE%2C+INCIDENT_ADDRESS%2C+GEO_X%2C+GEO_Y%2C+GEO_LON%2C+GEO_LAT%2C+DISTRICT_ID%2C+PRECINCT_ID%2C+NEIGHBORHOOD_ID%2C+IS_CRIME%2C+IS_TRAFFIC%2C+VICTIM_COUNT&returnGeometry=false&featureEncoding=esriDefault&multipatchOption=xyFootprint&maxAllowableOffset=&geometryPrecision=&outSR=&defaultSR=&datumTransformation=&applyVCSProjection=false&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnExtentOnly=false&returnQueryGeometry=false&returnDistinctValues=false&cacheHint=false&orderByFields=OBJECTID+desc&groupByFieldsForStatistics=&outStatistics=&having=&resultOffset=&resultRecordCount={init_num}&returnZ=false&returnM=false&returnExceededLimitFeatures=true&quantizationParameters=&sqlFormat=standard&f=pjson&token="
newest = f"https://services1.arcgis.com/zdB7qR0BtYrg0Xpl/ArcGIS/rest/services/ODC_CRIME_OFFENSES_P/FeatureServer/324/query?where=OBJECTID%3E%3D0&objectIds=&time=&geometry=&geometryType=esriGeometryEnvelope&inSR=&spatialRel=esriSpatialRelIntersects&resultType=standard&distance=0.0&units=esriSRUnit_Meter&relationParam=&returnGeodetic=false&outFields=OBJECTID%2C+INCIDENT_ID%2C+OFFENSE_ID%2C+OFFENSE_CODE%2C+OFFENSE_CODE_EXTENSION%2C+OFFENSE_TYPE_ID%2C+OFFENSE_CATEGORY_ID%2C+FIRST_OCCURRENCE_DATE%2C+LAST_OCCURRENCE_DATE%2C+REPORTED_DATE%2C+INCIDENT_ADDRESS%2C+GEO_X%2C+GEO_Y%2C+GEO_LON%2C+GEO_LAT%2C+DISTRICT_ID%2C+PRECINCT_ID%2C+NEIGHBORHOOD_ID%2C+IS_CRIME%2C+IS_TRAFFIC%2C+VICTIM_COUNT&returnGeometry=false&featureEncoding=esriDefault&multipatchOption=xyFootprint&maxAllowableOffset=&geometryPrecision=&outSR=&defaultSR=&datumTransformation=&applyVCSProjection=false&returnIdsOnly=false&returnUniqueIdsOnly=false&returnCountOnly=false&returnExtentOnly=false&returnQueryGeometry=false&returnDistinctValues=false&cacheHint=false&orderByFields=REPORTED_DATE+desc&groupByFieldsForStatistics=&outStatistics=&having=&resultOffset=&resultRecordCount={init_num}&returnZ=false&returnM=false&returnExceededLimitFeatures=true&quantizationParameters=&sqlFormat=standard&f=pjson&token="

r = requests.post(newest)
data = r.json()

values = []
entries = data['features']
timeToFilter = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0) - relativedelta(days=30)
reportedDateFilter = int(time.mktime(timeToFilter.timetuple()) * 1000)
print(reportedDateFilter)
for row in entries:
if(row["attributes"]["REPORTED_DATE"] is None):
continue
if(row["attributes"]["REPORTED_DATE"] < reportedDateFilter):
continue
rowdata = list(row["attributes"].values())

value = "("
Expand Down
3 changes: 2 additions & 1 deletion react-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@
},
"devDependencies": {
"@types/leaflet": "^1.9.11"
}
},
"proxy": "http://localhost:5000"
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react-frontend/public/android-chrome-512x512.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react-frontend/public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react-frontend/public/favicon-16x16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added react-frontend/public/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified react-frontend/public/favicon.ico
Binary file not shown.
4 changes: 2 additions & 2 deletions react-frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
content="Web site created for spotting reccent crimes in denver metropolitan area"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
Expand All @@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Betafish Crime Spotter</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
Expand Down
Binary file removed react-frontend/public/logo192.png
Binary file not shown.
Binary file removed react-frontend/public/logo512.png
Binary file not shown.
26 changes: 1 addition & 25 deletions react-frontend/public/manifest.json
Original file line number Diff line number Diff line change
@@ -1,25 +1 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
49 changes: 45 additions & 4 deletions react-frontend/src/App.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,50 @@
.App {
width: 80vw;
max-width: 1800px;
background-color: #2e2222;
display: flex;
width: 95vw;
flex-direction: column;
justify-content: center;
justify-content: start;
min-height: 100vh;
overflow: hidden;
}

#SectionContainer {
position: relative;
display: grid;
grid-template-columns: 1fr;
grid-template-rows: 1fr;
justify-content: center;
align-content: center;
}

#search-div {
opacity: 0%;
transform: translateX(-100%);
transition: ease-out 300ms;
grid-row: 1/2;
grid-column: 1/2;
justify-self: center;
z-index: 10;
}

#analytics-div {
opacity: 0%;
transform: translateX(100%);
transition: ease-out 300ms;
grid-row: 1/2;
grid-column: 1/2;
justify-self: center;
z-index: 10;
}

#search-div.sectionactive, #analytics-div.sectionactive {
opacity: 100%;
transform: translateX(0%);
transition: ease-in 300ms;
z-index: 20;
}

@media (max-width: 1000px) {
.App {
width: 100%;
}
}
2 changes: 1 addition & 1 deletion react-frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ import App from './App';

test('renders Database Dump Section', () => {
render(<App />);
const linkElement = screen.getByText(/Database Dump/i);
const linkElement = screen.getByText(/Analytics/i);
expect(linkElement).toBeInTheDocument();
});
19 changes: 14 additions & 5 deletions react-frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,39 @@ import React, { useState } from 'react';
import './App.css';
import Search from './search/Search';
import { GlobalState } from './types';
import Header from './Header';
import Analytics from './analytics/Analytics';


function App() {

const [globalState, setGlobalState] = useState<GlobalState>({
active: "search",
filters: {
long: -105.2668960437206,
lat: 40.00943069669764,
long: -105.2668960437206,
startDate: new Date().toJSON().slice(0, 10),
endDate: new Date().toJSON().slice(0, 10)
},
fetched: false,
map: {
lat: 40.00943069669764,
long: -105.2668960437206
},
crimeList: {
page_no: 1,
page_size: 10,
page_size: 20,
data: []
}
});

console.log(globalState)

return (
<div className="App">
<Search globalState={globalState} setGlobalState={setGlobalState}/>
<Header globalState={globalState} setGlobalState={setGlobalState} />
<div id="SectionContainer">
<Search globalState={globalState} setGlobalState={setGlobalState} />
<Analytics globalState={globalState} setGlobalState={setGlobalState} />
</div>
</div>
);
}
Expand Down
30 changes: 30 additions & 0 deletions react-frontend/src/Header.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#logosplash {
display: flex;
align-items: center;
}
#logo {
width: 70px;
margin: 0 20px;
}

#navbar {
display: flex;
align-items: center;
justify-content: center;
}

.navbutton {
margin: 0 10px;
padding: 10px 10px;
cursor: pointer;
transition: ease-in 100ms;
}

input[type=radio] + label.navbutton {
font-weight: 400;
border-bottom: #00000000 4px solid;
}
input[type=radio]:checked + label.navbutton {
font-weight: 900;
border-bottom: var(--accent-color) 4px solid;
}
28 changes: 24 additions & 4 deletions react-frontend/src/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,30 @@
import React from "react";
import React, { ChangeEvent } from "react";

import "./Header.css"
import { GlobalStateProps } from "./types";

const Header = (props: GlobalStateProps) => {
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
props.setGlobalState(prev => {
return {
...prev,
active: e.target.value
}
})
}

const Header = () => {
return (
<div id="header">
<p>Search Crime</p>
<p>Overview</p>
<div id="logosplash">
<img id="logo" src="/android-chrome-512x512.png" alt="glass"/>
<h1>Betafish Crime Spotter</h1>
</div>
<div id="navbar">
<input onChange={handleChange} id="navsearch" type="radio" name="nav" value="search" hidden checked={props.globalState.active === "search"}/>
<label className="navbutton" htmlFor="navsearch">Search</label>
<input onChange={handleChange} id="navanalytics" type="radio" name="nav" value="analytics" hidden checked={props.globalState.active === "analytics"}/>
<label className="navbutton" htmlFor="navanalytics">Analytics</label>
</div>
</div>
);
}
Expand Down
11 changes: 11 additions & 0 deletions react-frontend/src/analytics/Analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { GlobalStateProps } from "../types";

const Analytics = (props: GlobalStateProps) => {
return (
<div id="analytics-div" className={props.globalState.active === "analytics"? "sectionactive":""}>
<p>Something</p>
</div>
);
}

export default Analytics;
Loading
Loading