Add date-range filters to map

This commit is contained in:
Paul Bienkowski 2022-07-22 13:29:51 +02:00
parent 7716da8844
commit e0cb36565a
4 changed files with 157 additions and 22 deletions

View file

@ -1,5 +1,7 @@
from gzip import decompress from gzip import decompress
from sqlite3 import connect from sqlite3 import connect
import dateutil.parser
from sanic.exceptions import Forbidden from sanic.exceptions import Forbidden
from sanic.response import raw from sanic.response import raw
@ -46,6 +48,10 @@ async def tiles(req, zoom: int, x: int, y: str):
raise Forbidden() raise Forbidden()
user_id = req.ctx.user.id user_id = req.ctx.user.id
parse_date = lambda s: dateutil.parser.parse(s)
start = req.ctx.get_single_arg("start", default=None, convert=parse_date)
end = req.ctx.get_single_arg("end", default=None, convert=parse_date)
tile = await req.ctx.db.scalar( tile = await req.ctx.db.scalar(
text( text(
f"select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time) as b(data, key);" f"select data from getmvt(:zoom, :x, :y, :user_id, :min_time, :max_time) as b(data, key);"
@ -54,8 +60,8 @@ async def tiles(req, zoom: int, x: int, y: str):
x=int(x), x=int(x),
y=int(y), y=int(y),
user_id=user_id, user_id=user_id,
min_time=None, min_time=start,
max_time=None, max_time=end,
) )
) )

View file

@ -32,6 +32,12 @@ const ROAD_ATTRIBUTE_OPTIONS = [
"zone", "zone",
]; ];
const DATE_FILTER_MODES = [
{ value: "none", key: "none", text: "All time" },
{ value: "range", key: "range", text: "Start and end range" },
{ value: "threshold", key: "threshold", text: "Before/after comparison" },
];
type User = Object; type User = Object;
function LayerSidebar({ function LayerSidebar({
@ -48,7 +54,13 @@ function LayerSidebar({
baseMap: { style }, baseMap: { style },
obsRoads: { show: showRoads, showUntagged, attribute, maxCount }, obsRoads: { show: showRoads, showUntagged, attribute, maxCount },
obsEvents: { show: showEvents }, obsEvents: { show: showEvents },
filters: { currentUser: filtersCurrentUser }, filters: {
currentUser: filtersCurrentUser,
dateMode,
startDate,
endDate,
thresholdAfter,
},
} = mapConfig; } = mapConfig;
return ( return (
@ -209,11 +221,18 @@ function LayerSidebar({
</> </>
)} )}
<Divider /> <Divider />
<List.Item> <List.Item>
<Header as="h4" style={{ marginBottom: 8 }}> <Header as="h4">Filters</Header>
Filter </List.Item>
</Header>
{login && ( {login && (
<>
<List.Item>
<Header as="h5">User data</Header>
</List.Item>
<List.Item>
<Checkbox <Checkbox
toggle toggle
size="small" size="small"
@ -224,9 +243,91 @@ function LayerSidebar({
} }
label="Show only my own data" label="Show only my own data"
/> />
)}
{!login && <div>No filters available without login.</div>}
</List.Item> </List.Item>
<List.Item>
<Header as="h5">Date range</Header>
</List.Item>
<List.Item>
<Select
id="filters.dateMode"
options={DATE_FILTER_MODES}
value={dateMode ?? "none"}
onChange={(_e, { value }) =>
setMapConfigFlag("filters.dateMode", value)
}
/>
</List.Item>
{dateMode == "range" && (
<List.Item>
<Input
type="date"
size="small"
id="filters.startDate"
onChange={(_e, { value }) =>
setMapConfigFlag("filters.startDate", value)
}
value={startDate ?? null}
label="Start"
/>
</List.Item>
)}
{dateMode == "range" && (
<List.Item>
<Input
type="date"
size="small"
id="filters.endDate"
onChange={(_e, { value }) =>
setMapConfigFlag("filters.endDate", value)
}
value={endDate ?? null}
label="End"
/>
</List.Item>
)}
{dateMode == "threshold" && (
<List.Item>
<Input
type="date"
size="small"
id="filters.startDate"
value={startDate ?? null}
onChange={(_e, { value }) =>
setMapConfigFlag("filters.startDate", value)
}
label="Threshold"
/>
</List.Item>
)}
{dateMode == "threshold" && (
<List.Item>
<span>
Before{" "}
<Checkbox
toggle
size="small"
checked={thresholdAfter ?? false}
onChange={() =>
setMapConfigFlag(
"filters.thresholdAfter",
!thresholdAfter
)
}
id="filters.thresholdAfter"
/>{" "}
After
</span>
</List.Item>
)}
</>
)}
{!login && <List.Item>No filters available without login.</List.Item>}
</List> </List>
</div> </div>
); );

View file

@ -160,9 +160,29 @@ function MapPage({ login }) {
} }
const tiles = obsMapSource?.tiles?.map( const tiles = obsMapSource?.tiles?.map(
(tileUrl: string) => (tileUrl: string) => {
tileUrl + const query = new URLSearchParams()
(login && mapConfig.filters.currentUser ? `?user=${login.username}` : "") if (login) {
if (mapConfig.filters.currentUser) {
query.append('user', login.username)
}
if (mapConfig.filters.dateMode === "range") {
if (mapConfig.filters.startDate) {
query.append('start', mapConfig.filters.startDate)
}
if (mapConfig.filters.endDate) {
query.append('end', mapConfig.filters.endDate)
}
} else if (mapConfig.filters.dateMode === "threshold") {
if (mapConfig.filters.startDate) {
query.append(mapConfig.filters.thresholdAfter ? 'start' : 'end', mapConfig.filters.startDate)
}
}
}
const queryString = String(query)
return tileUrl + (queryString ? '?' : '') + queryString
}
); );
return ( return (

View file

@ -29,6 +29,10 @@ export type MapConfig = {
}; };
filters: { filters: {
currentUser: boolean; currentUser: boolean;
dateMode: "none" | "range" | "threshold";
startDate?: null | string;
endDate?: null | string;
thresholdAfter?: null | boolean;
}; };
}; };
@ -47,6 +51,10 @@ export const initialState: MapConfig = {
}, },
filters: { filters: {
currentUser: false, currentUser: false,
dateMode: "none",
startDate: null,
endDate: null,
thresholdAfter: true,
}, },
}; };