diff --git a/CHANGELOG.md b/CHANGELOG.md
index 821c81e..d71c65e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# Changelog
+## 0.4.1 (TBD)
+
+### Features
+
+* Add page for exporting data through web frontend
+
## 0.4.0
### Improvements
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ab116fa..17bf511 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -11,6 +11,7 @@ import {useConfig} from 'config'
import styles from './App.module.less'
import {
+ ExportPage,
HomePage,
LoginRedirectPage,
LogoutPage,
@@ -84,6 +85,10 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
Tracks
+
+ Export
+
+
{login ? (
<>
@@ -127,6 +132,9 @@ const App = connect((state) => ({login: state.login}))(function App({login}) {
+
+
+
diff --git a/frontend/src/pages/ExportPage/index.tsx b/frontend/src/pages/ExportPage/index.tsx
new file mode 100644
index 0000000..3df28ca
--- /dev/null
+++ b/frontend/src/pages/ExportPage/index.tsx
@@ -0,0 +1,167 @@
+import React, { useState, useCallback, useMemo } from "react";
+import { Source, Layer } from "react-map-gl";
+import _ from "lodash";
+import { Button, Form, Dropdown, Header } from "semantic-ui-react";
+
+import { useConfig } from "config";
+import { Page, Map } from "components";
+
+const BoundingBoxSelector = React.forwardRef(
+ ({ value, name, onChange }, ref) => {
+ const [pointNum, setPointNum] = useState(0);
+ const [point0, setPoint0] = useState(null);
+ const [point1, setPoint1] = useState(null);
+
+ const onClick = (e) => {
+ if (pointNum == 0) {
+ setPoint0(e.lngLat);
+ } else {
+ setPoint1(e.lngLat);
+ }
+ setPointNum(1 - pointNum);
+ };
+
+ React.useEffect(() => {
+ if (!point0 || !point1) return;
+ const bbox = `${point0[0]},${point0[1]},${point1[0]},${point1[1]}`;
+ if (bbox !== value) {
+ onChange(bbox);
+ }
+ }, [point0, point1]);
+
+ React.useEffect(() => {
+ const [p00, p01, p10, p11] = value
+ .split(",")
+ .map((v) => Number.parseFloat(v));
+ if (!point0 || point0[0] != p00 || point0[1] != p01)
+ setPoint0([p00, p01]);
+ if (!point1 || point1[0] != p10 || point1[1] != p11)
+ setPoint1([p10, p11]);
+ }, [value]);
+
+ return (
+
+ );
+ }
+);
+
+const MODES = [
+ {
+ key: "events",
+ text: "Events",
+ value: "events",
+ },
+];
+
+const FORMATS = [
+ {
+ key: "geojson",
+ text: "GeoJSON",
+ value: "geojson",
+ },
+ {
+ key: "shapefile",
+ text: "Shapefile (ZIP)",
+ value: "shapefile",
+ },
+];
+
+export default function ExportPage() {
+ const [mode, setMode] = useState("events");
+ const [bbox, setBbox] = useState("8.294678,49.651182,9.059601,50.108249");
+ const [fmt, setFmt] = useState("geojson");
+ const config = useConfig();
+ const exportUrl = `${config?.apiUrl}/export/events?bbox=${bbox}&fmt=${fmt}`;
+ return (
+
+
+
+
+ This page allows you to export parts of the public dataset. Please note
+ the license.
+
+
+
+
+ setMode(value)}
+ />
+
+
+
+
+ setFmt(value)}
+ />
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/pages/index.js b/frontend/src/pages/index.js
index 070cbd9..07233ed 100644
--- a/frontend/src/pages/index.js
+++ b/frontend/src/pages/index.js
@@ -1,3 +1,4 @@
+export {default as ExportPage} from './ExportPage'
export {default as HomePage} from './HomePage'
export {default as LoginRedirectPage} from './LoginRedirectPage'
export {default as LogoutPage} from './LogoutPage'