Skip to content

Commit

Permalink
Merge pull request #67 from tsdataclinic/tileserver
Browse files Browse the repository at this point in the history
Add a tileserver
  • Loading branch information
indraneel authored Feb 12, 2024
2 parents 5f2c44b + ad7db82 commit 65d1517
Show file tree
Hide file tree
Showing 11 changed files with 186 additions and 109 deletions.
3 changes: 3 additions & 0 deletions app/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ POSTGRES_DB=''
# if not production, comment it out
REACT_APP_DEV_BACKEND_URI=''
# REACT_APP_PROD_BACKEND_URI=''

REACT_APP_DEV_TILESERVER_URI=''
# REACT_APP_PROD_TILESERVER_URI=''
59 changes: 59 additions & 0 deletions app/backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,65 @@ async def shutdown():
async def ping():
return {"message": "pong"}

@app.get("/route-summary/{city}/{route}")
async def get_route_summary(city: str, route: str):
col_names = [
"flood_risk_category",
"heat_risk_category",
"fire_risk_category",
"access_to_hospital_category",
"job_access_category",
"worker_vulnerability_category",
]

output = {
"count": 0,
"agency": "",
"route": route,
"flood_risk_category": [0, 0, 0],
"heat_risk_category": [0, 0, 0],
"fire_risk_category": [0, 0, 0],
"access_to_hospital_category": [0, 0, 0],
"job_access_category": [0, 0, 0],
"worker_vulnerability_category": [0, 0, 0],
}

connection = app.state.db_pool.getconn()
cursor = connection.cursor()

agency_query = f"""
SELECT
agencies_serviced
FROM public.stop_features
WHERE '{route}' = any(string_to_array(routes_serviced, ','))
AND city = '{city}'
"""
cursor.execute(agency_query)
agency = cursor.fetchone()[0]
if agency:
output["agency"] = agency

for col in col_names:
stops_on_route_query = f"""
SELECT
{col}, count({col})
FROM public.stop_features
WHERE '{route}' = any(string_to_array(routes_serviced, ','))
AND city = '{city}'
GROUP BY {col}
"""
cursor.execute(stops_on_route_query)
stops_on_route_result = cursor.fetchall()
summary_values = [0,0,0]
for s in stops_on_route_result:
summary_values[s[0]] = s[1]
output[col] = summary_values

cursor.close()
app.state.db_pool.putconn(connection)
output["count"] = sum(output["flood_risk_category"])
return output

@app.get("/available-routes")
async def get_all_available_routes():
connection = app.state.db_pool.getconn()
Expand Down
13 changes: 12 additions & 1 deletion app/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,21 @@ services:
# - ./11-create_tables.sql:/docker-entrypoint-initdb.d/11-create_tables.sql
# - ./12-insert_initial_data.sql:/docker-entrypoint-initdb.d/12-insert_initial_data.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
test: ["CMD-SHELL", "pg_isready -U postgres -d trec"]
interval: 5s
timeout: 5s
retries: 5
martin:
image: ghcr.io/maplibre/martin:v0.11.6
restart: unless-stopped
ports:
- "3002:3000"
environment:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_DB}/${POSTGRES_DB}
depends_on:
db:
condition: service_healthy


volumes:
postgis_data:
5 changes: 3 additions & 2 deletions app/src/components/ContextPane.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ function ContextPane({
</div>
<hr />
<div className="text-lg flex justify-between">
<b>Filter by Transit Line</b>
<button onClick={() => setSelectedRoutes([])}>Reset</button>
<b>Transit Lines</b>
{/* <button onClick={() => setSelectedRoutes([])}>Reset</button> */}
</div>
{selectedRoutes.length > 0 ? (
<div className="grid grid-cols-4 px-3 gap-1">
Expand Down Expand Up @@ -197,6 +197,7 @@ function ContextPane({
}
setSelectedRoutes(newRoutes);
}}
disabled={true}
checked={selectedRoutes
.map(r => r.routeServiced)
.includes(route_serviced)}
Expand Down
17 changes: 12 additions & 5 deletions app/src/components/MainPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,17 @@ import { Cities } from '../libs/cities';
import { useAvailableCities } from '../hooks/useAvailableCities';
import { useAvailableRoutes } from '../hooks/useAvailableRoutes';
import usePrevious from '../hooks/usePrevious';
import { useRemoteRouteSummary } from '../hooks/useRemoteRouteSummary';

const BACKEND_URI = process.env.REACT_APP_PROD_BACKEND_URI ?? process.env.REACT_APP_DEV_BACKEND_URI;
const BACKEND_TILESERVER_URI = process.env.REACT_APP_PROD_TILESERVER_URI ?? process.env.REACT_APP_DEV_TILESERVER_URI;

export type Layer = {
id: number;
layerName: string;
sourceLayer?: string;
layerURL: string;
layerURL?: string;
tileURL?: string;
isVisible: boolean;
hideToggle: boolean;
city?: Cities;
Expand Down Expand Up @@ -54,14 +57,17 @@ const AVAILABLE_LAYERS: Record<string, Layer> = {
'1': {
id: 1,
layerName: 'Transit Stops',
layerURL: `${BACKEND_URI}/stop_features.geojson`,
tileURL: `${BACKEND_TILESERVER_URI}/stop_features`,
sourceLayer: 'stop_features',
isVisible: true,
hideToggle: true,
},
'2': {
id: 2,
layerName: 'Hospitals',
layerURL: `${BACKEND_URI}/hospitals.geojson`,
tileURL: `${BACKEND_TILESERVER_URI}/hospitals`,
sourceLayer: 'hospitals',
isVisible: true,
hideToggle: false,
},
Expand Down Expand Up @@ -161,7 +167,7 @@ export default function MainPage(): JSX.Element {

const [layers, setLayers] = useState(AVAILABLE_LAYERS);
let remoteLayers = useRemoteLayers(layers, selectedCity);
let routeSummary = useRouteSummary(remoteLayers, detailedRoutes);
let {data: routeSummaryNew, status} = useRemoteRouteSummary(detailedRoutes.city, detailedRoutes.routeServiced);

let sourceLayerConfigs = useSourceLayerConfigs(
selectedProperties,
Expand Down Expand Up @@ -255,9 +261,10 @@ export default function MainPage(): JSX.Element {
selectedRoutes={selectedRoutes}
setSelectedRoutes={setSelectedRoutes}
/>
{detailedRoutes.city != '' && (
{detailedRoutes.city !== '' && (
<RouteSummaryPane
routeSummary={routeSummary}
status={status}
routeSummary={routeSummaryNew}
detailedRoutes={detailedRoutes}
setDetailedRoutes={setDetailedRoutes}
/>
Expand Down
67 changes: 40 additions & 27 deletions app/src/components/MapComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ function MapComponent({
const paintLayer = useCallback(
(layer: Layer, remoteLayer: RemoteLayer,
selectedCity: string, previousSelectedCity: string) => {

if (!map.current) {
return;
}
Expand All @@ -58,23 +57,29 @@ function MapComponent({
map.current.removeSource(layer.layerName);
}
} else {

// if a layer is visible, add back the source if it
// doesn't exist already
if (!map.current?.getSource(layer.layerName)) {
if (layer.layerURL.includes('geojson')) {
if (layer.tileURL) {
map.current.addSource(layer.layerName, {
type: 'geojson',
data: remoteLayer.data,
type: 'vector',
url: layer.tileURL
});
} else if (layer.layerURL.includes('mapbox://')) {
}
else if (layer.layerURL && layer.layerURL.includes('geojson')) {
// map.current.addSource(layer.layerName, {
// type: 'geojson',
// });
} else if (layer.layerURL && layer.layerURL.includes('mapbox://')) {
map.current.addSource(layer.layerName, {
type: 'vector',
url: layer.layerURL,
});
} else {
map.current.addSource(layer.layerName, {
type: 'vector',
tiles: [layer.layerURL],
tiles: [layer.tileURL!]
});
}
}
Expand All @@ -88,7 +93,15 @@ function MapComponent({

// add back the layer if it's not already in the map
if (!map.current.getLayer(slConfig.layerId)) {
if (layer.layerURL.includes('geojson')) {
if (layer.tileURL) {
map.current.addLayer({
id: slConfig.layerId,
type: slConfig.layerType,
source: layer.layerName,
'source-layer': layer.sourceLayer,
}, 'z-index-1');
}
else if (layer.layerURL && layer.layerURL.includes('geojson')) {
if (layer.layerName === 'Hospitals') {
map.current.addLayer({
id: slConfig.layerId,
Expand All @@ -108,7 +121,7 @@ function MapComponent({
type: slConfig.layerType,
source: layer.layerName,
'source-layer': layer.sourceLayer,
}, 'z-index-2');
}, 'z-index-1');
}
}

Expand Down Expand Up @@ -211,26 +224,26 @@ function MapComponent({
const features = map.current
.queryRenderedFeatures(e.point)
.filter(f => layerNames.includes(f.source));
if (features.length > 0) {
const feature = features[0];
const tooltipNode = document.createElement('div');
const root = createRoot(tooltipNode);

root.render(
<Tooltip
feature={feature}
onDismiss={() => {
tooltipRef.current.remove();
}}
setDetailedRoutes={setDetailedRoutes}
/>,
);
if (features.length > 0) {
const feature = features[0];
const tooltipNode = document.createElement('div');
const root = createRoot(tooltipNode);
root.render(
<Tooltip
feature={feature}
onDismiss={() => {
tooltipRef.current.remove();
}}
setDetailedRoutes={setDetailedRoutes}
/>,
);

tooltipRef.current
.setLngLat(e.lngLat)
.setDOMContent(tooltipNode)
.addTo(map.current);
}
tooltipRef.current
.setLngLat(e.lngLat)
.setDOMContent(tooltipNode)
.addTo(map.current);
}
});
}
}, [layers, paintLayer, previousSelectedCity, remoteLayers, selectedCity, setDetailedRoutes]);
Expand Down
64 changes: 20 additions & 44 deletions app/src/components/RouteSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,19 @@
import {SelectedRoute } from './MainPage';
import {RouteSummary} from '../hooks/useRouteSummary';
import * as IconType from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import BarChart from './ui/Chart'
import { RouteSummaryResponse } from '../hooks/useRemoteRouteSummary';

function mode(arr:Array<String>){
return arr.sort((a,b) =>
arr.filter(v => v===a).length
- arr.filter(v => v===b).length
).pop();
}

function LineLabel(props: {
children: React.ReactNode;
label: string;
}): JSX.Element {
const { children, label } = props;
return (
<div className="flex flex-col pl-4 space-y-2">{label} "Line"</div>
);
}

type RouteProps = {
routeSummary: Array<RouteSummary>;
// routes: Array<Record<string, RouteRecord>>;
status: string;
routeSummary: RouteSummaryResponse;
detailedRoutes: SelectedRoute;
setDetailedRoutes: React.Dispatch<React.SetStateAction<SelectedRoute>>;
};

function RouteSummaryPane({
status,
routeSummary,
detailedRoutes,
setDetailedRoutes,
Expand All @@ -38,43 +23,34 @@ function RouteSummaryPane({
id="SummaryPane"
className="bg-white min-w-fit h-full shadow flex flex-col overflow-y-scroll"
>
{/* min-w-max max-w-sm */}

<div className="p-2 border-b border-b-slate-400">
<FontAwesomeIcon
icon={IconType.faArrowLeft}
onClick={()=>{setDetailedRoutes({city:'',routeType:'',routeServiced:''})}}
className="text-base font-xl hover:bg-slate-200 cursor-pointer transition-colors pt-3"/>
<b className="pl-4 pt-2">{detailedRoutes.routeServiced}</b>
<b className="pl-4 pt-2">
{status === 'loading' ? 'Loading...' : detailedRoutes.routeServiced}
</b>
<div className="flex flex-col pl-7 space-y-2">
{mode(routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['agency'].split(','))}
{status === 'loading' ? 'Loading...' : detailedRoutes.routeType}
</div>
<div className="flex flex-col pl-7 space-y-2">
{detailedRoutes.routeType} Route
{status === 'loading' ? 'Loading...' : routeSummary.agency }
</div>

</div>


<div className="flex flex-col pt-4 pl-4 space-y-2"><b>Total Stops</b></div>

<div className="flex flex-col pt-4 pl-4 space-y-2">
<b>Total Stops</b></div>
<div className="flex flex-col pt-2 pl-4 space-y-2">
<b className="text-xl">{JSON.stringify(routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['count'])}</b>
<b className="text-xl">
{routeSummary?.count}
</b>
</div>

<BarChart label='Flood Risk' data={routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['flood_risk']}></BarChart>

<BarChart label='Heat Risk' data={routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['heat_risk']}></BarChart>

<BarChart label='Fire Risk' data={routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['fire_risk']}></BarChart>

<BarChart label='Access to Hospitals' data={routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['hospital_access']}></BarChart>

<BarChart label='Access to Jobs' data={routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['job_access']}></BarChart>

<BarChart label='Vulnerable workers' data={routeSummary.filter(function(e:any) {return e.route == detailedRoutes.routeServiced})[0]['worker_vulnerability']}></BarChart>


<BarChart label='Flood Risk' data={status === 'success' ? routeSummary.flood_risk_category : [0,0,0]}></BarChart>
<BarChart label='Heat Risk' data={status === 'success' ? routeSummary.heat_risk_category : [0,0,0]}></BarChart>
<BarChart label='Fire Risk' data={status === 'success' ? routeSummary.fire_risk_category : [0,0,0]}></BarChart>
<BarChart label='Access to Hospitals' data={status === 'success' ? routeSummary.access_to_hospital_category : [0,0,0]}></BarChart>
<BarChart label='Access to Jobs' data={status === 'success' ? routeSummary.job_access_category : [0,0,0]}></BarChart>
<BarChart label='Vulnerable workers' data={status === 'success' ? routeSummary.worker_vulnerability_category : [0,0,0]}></BarChart>
</div>
);

Expand Down
Loading

0 comments on commit 65d1517

Please sign in to comment.