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

Restore query state from previous results file #97

Merged
merged 9 commits into from
Jan 17, 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
31 changes: 31 additions & 0 deletions backend/app/get_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import json
from app.db import getConnection

sql = '''
SELECT
cg_nodes.node_id::int,
ST_AsGeoJSON(cg_nodes.geom) AS geom,
array_agg(DISTINCT InitCap(streets.st_name)) FILTER (WHERE streets.st_name IS NOT NULL) AS street_names
FROM congestion.network_nodes AS cg_nodes
JOIN here.routing_nodes_21_1 AS here_nodes USING (node_id)
JOIN here_gis.streets_att_21_1 AS streets USING (link_id)
WHERE node_id = %(node_id)s
GROUP BY
cg_nodes.node_id,
cg_nodes.geom;
'''

# TODO code could use some tidying up
def get_node(node_id):
with getConnection() as connection:
with connection.cursor() as cursor:
cursor.execute(sql, {"node_id": node_id})
nodes = []
for node_id, geojson, street_names in cursor.fetchall():
nodes.append( {
'node_id': node_id,
'street_names': street_names,
'geometry': json.loads(geojson)
} )
connection.close()
return nodes[0] if len(nodes) > 0 else {}
9 changes: 9 additions & 0 deletions backend/app/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from app import app
from app.db import getConnection
from app.get_closest_nodes import get_closest_nodes
from app.get_node import get_node
from app.get_travel_time import get_travel_time

from app.get_links import get_links
Expand All @@ -25,6 +26,14 @@ def closest_node(longitude,latitude):
return jsonify({'error': "Longitude and latitude must be decimal numbers!"})
return jsonify(get_closest_nodes(longitude,latitude))

# test URL /node/30357505
@app.route('/node/<node_id>', methods=['GET'])
def node(node_id):
try:
node_id = int(node_id)
except:
return jsonify({'error': "node_id should be an integer"})
return jsonify(get_node(node_id))

# test URL /link-nodes/30421154/30421153
#shell function - outputs json for use on frontend
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/Sidebar/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@ import FactorContainer from './FactorContainer'
import BigButton from './BigButton'
import FactorList from './FactorList'
import { TravelTimeQuery } from '../travelTimeQuery.js'
import { restoreStateFromFile } from './restoreStateFromFile.js'
import './sidebar.css'

export default function SidebarContent(){
const { data, logActivity } = useContext(DataContext)
return (
<div className="sidebarContent">
<div className="sidebarContent"
onDragEnter={ e => { e.stopPropagation(); e.preventDefault() } }
onDragOver={ e => { e.stopPropagation(); e.preventDefault() } }
onDrop={ event => {
restoreStateFromFile(event,data,logActivity)
.then( logActivity('state restored from file') ) // not working?
} }
>
<Welcome/>
<CorridorsContainer/>
<div className='big-math-symbol'>&#xd7;</div>
Expand Down Expand Up @@ -40,7 +49,7 @@ function Results(){
setIsFetchingData(true)
data.fetchAllResults().then( () => {
setIsFetchingData(false)
setResults(data.travelTimeQueries)
setResults(data.travelTimeQueries)
} )
}}>
Submit Query
Expand Down
75 changes: 75 additions & 0 deletions frontend/src/Sidebar/restoreStateFromFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Intersection } from '../intersection.js'
import { domain } from '../domain.js'

const URIpattern = /\/(?<startNode>\d+)\/(?<endNode>\d+)\/(?<startTime>\d+)\/(?<endTime>\d+)\/(?<startDate>\d{4}-\d{2}-\d{2})\/(?<endDate>\d{4}-\d{2}-\d{2})\/(?<holidays>true|false)\/(?<dow>\d+)/g

export async function restoreStateFromFile(fileDropEvent,stateData,logActivity){
fileDropEvent.stopPropagation()
fileDropEvent.preventDefault()

// only handle one file at a time
let file = fileDropEvent.dataTransfer.files[0]

return file.text()
.then( textData => {
// should be a list of objects each with a URI property
let URIs = [...textData.matchAll(URIpattern)].map(m=>m.groups)
distinctPairs(URIs,'startNode','endNode')
.forEach( ({startNode,endNode}) => {
let corridor = stateData.createCorridor()
Promise.all(
[startNode,endNode].map(node_id => {
return fetch(`${domain}/node/${node_id}`)
.then( resp => resp.json() )
.then( node => new Intersection( {
id: node.node_id,
lat: node.geometry.coordinates[1],
lng: node.geometry.coordinates[0],
streetNames: node.street_names
} )
)
} )
).then( intersections => {
corridor.addIntersection(intersections[0],logActivity)
corridor.addIntersection(intersections[1],logActivity)
})
} )
distinctPairs(URIs,'startTime','endTime')
.forEach( ({startTime,endTime}) => {
let timeRange = stateData.createTimeRange()
timeRange.setStartTime(startTime)
timeRange.setEndTime(endTime)
} )
distinctPairs(URIs,'startDate','endDate')
.forEach( ({startDate,endDate}) => {
let dateRange = stateData.createDateRange()
dateRange.setStartDate(new Date(Date.parse(startDate)))
dateRange.setEndDate(new Date(Date.parse(endDate)))
} )
// holiday inclusion
let holidays = new Set(URIs.map(uri => uri.holidays))
if(holidays.has('true') && holidays.has('false')){
stateData.includeAndExcludeHolidays()
}else if(holidays.has('true')){
stateData.includeHolidays()
}else{
stateData.excludeHolidays()
}
// days of week
// TODO: drop the default selection?
[... new Set(URIs.map(uri=>uri.dow))].forEach( dowsString => {
let daysFactor = stateData.createDays()
daysFactor.setFromSet(new Set(dowsString.split('').map(Number)))
} )
} )
}

// get distinct value pairs from a list of objects by their property names
// all are strings
function distinctPairs(list, prop1, prop2){
let distinctKeys = new Set( list.map(o => `${o[prop1]} | ${o[prop2]}`) )
return [...distinctKeys].map( k => {
let vals = k.split(' | ')
return { [prop1]: vals[0], [prop2]: vals[1] }
} )
}
5 changes: 5 additions & 0 deletions frontend/src/days.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ export class Days extends Factor {
hasDay(number){
return this.#days.has(parseInt(number))
}
setFromSet(dowSet){
const validDays = new Set([...weekday,...weekend])
let validDowSet = new Set([...dowSet].filter(v => validDays.has(v)))
this.#days = validDowSet
}
get name(){
if(this.#days.size == 7){
return 'all days'
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/spatialData.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,25 @@ export class SpatialData {
let corridor = new Corridor(this)
this.#factors.push(corridor)
corridor.activate()
return corridor
}
createTimeRange(){
let tr = new TimeRange(this)
this.#factors.push(tr)
tr.activate()
return tr
}
createDateRange(){
let dr = new DateRange(this)
this.#factors.push(dr)
dr.activate()
return dr
}
createDays(){
let days = new Days(this)
this.#factors.push(days)
days.activate()
return days
}
get segments(){
return this.corridors.flatMap( c => c.segments )
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/timeRange.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class TimeRange extends Factor {
return <TimeRangeElement timeRange={this}/>
}
static parseTime(timeString){
let match = timeString.match(/^(?<hours>\d{2})$/)
let match = timeString.match(/^(?<hours>\d{1,2})$/)
if(match){
let {hours} = match.groups
return new Date(1970, 1, 1, parseInt(hours))
Expand Down