Skip to content

Commit ac17c36

Browse files
Merge pull request #526 from hotosm/feature/dc-stats
Feature : Data completeness stats
2 parents c1b9154 + da80e3b commit ac17c36

File tree

11 files changed

+139
-13
lines changed

11 files changed

+139
-13
lines changed

docs/setup-development.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ Most of these environment variables have reasonable default settings.
111111
- `GARMIN_CONFIG`, `GARMIN_MKGMAP` absolute paths to garmin JARs
112112
- `OVERPASS_API_URL` url of Overpass api endpoint
113113

114-
- `RAW_DATA_API_URL` url of Galaxy api endpoint
114+
- `RAW_DATA_API_URL` url of Raw data api endpoint
115115

116116
- `DATABASE_URL` Database URL. Defaults to `postgres:///exports`
117117
- `DEBUG` Whether to enable debug mode. Defaults to `False` (production).

requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mercantile~=0.10.0
1111
psycopg2
1212
python3-openid==3.2.0
1313
social-auth-app-django==5.4.0
14-
social-auth-core==4.4.2 ### Upgrade this to include oauth2
14+
social-auth-core @ git+https://github.com/kshitijrajsharma/social-core.git ### Upgrade this to include osm oauth2 when released
1515
pytz
1616
pyyaml>=5.3
1717
raven

ui/app/actions/exports.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ export const getOverpassTimestamp = () => (dispatch, getState) => {
170170

171171
export const getGalaxyTimestamp = () => (dispatch, getState) => {
172172
return axios({
173-
baseURL: "https://api-prod.raw-data.hotosm.org/v1",
174-
url: "/status/"
173+
baseURL: window.RAW_DATA_API_URL,
174+
url: "v1/status/"
175175
})
176176
.then(response =>
177177
dispatch({

ui/app/actions/meta.js

+4
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ if (window.location.port) {
1010
hostname += `:${window.location.port}`;
1111
}
1212

13+
if (window.RAW_DATA_API_URL == null) {
14+
window.RAW_DATA_API_URL = process.env.RAW_DATA_API_URL;
15+
}
16+
1317
if (window.EXPORTS_API_URL == null) {
1418
window.EXPORTS_API_URL = process.env.EXPORTS_API_URL;
1519

ui/app/app.js

+2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import Home from "./components/Home";
2525
import NavBar from "./components/NavBar";
2626
import Stats from "./components/Stats";
2727
import Banner from "./components/Banner";
28+
import Message from "./components/Message.js";
2829
import { requireAuth } from "./components/utils";
2930

3031
import "@blueprintjs/core/dist/blueprint.css";
@@ -49,6 +50,7 @@ export default ({ history }) => {
4950
<div style={{ height: "100%" }}>
5051
<Auth />
5152
<NavBar />
53+
<Message/>
5254
<Banner />
5355
<Switch>
5456
<Route path="/authorized" component={Authorized} />

ui/app/components/ExportForm.js

+83-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import area from "@turf/area";
2+
import axios from "axios";
23
import bbox from "@turf/bbox";
34
import React, { Component } from "react";
45
import { Col, Nav, Panel, Row } from "react-bootstrap";
@@ -8,7 +9,6 @@ import { Redirect, Route, Switch } from "react-router";
89
import { NavLink } from "react-router-dom";
910
import { Fields, formValueSelector, reduxForm } from "redux-form";
1011
import { pointToTile } from "tilebelt";
11-
1212
import ChooseFormats from "./ChooseFormats";
1313
import DescribeExport from "./DescribeExport";
1414
import ExportAOIField from "./ExportAOIField";
@@ -128,6 +128,86 @@ export class ExportForm extends Component {
128128
};
129129
}
130130

131+
async fetchData(geometry) {
132+
const url = window.RAW_DATA_API_URL + "v1/stats/polygon/";
133+
try {
134+
const response = await axios.post(url, {
135+
geometry: geometry
136+
}, {
137+
headers: {"Content-Type": "application/json"}
138+
});
139+
140+
if (response.data) {
141+
142+
this.setState({ fetchedInfo: response.data });
143+
}
144+
} catch (error) {
145+
console.error("Failed to fetch summary data", error);
146+
147+
}
148+
}
149+
150+
componentDidUpdate(prevProps) {
151+
if (this.props.formValues.the_geom !== prevProps.formValues.the_geom) {
152+
this.fetchData(this.props.formValues.the_geom);
153+
}
154+
}
155+
156+
renderFetchedInfo() {
157+
const { fetchedInfo } = this.state;
158+
if (!this.props.formValues.the_geom) return null;
159+
if (!fetchedInfo) return null;
160+
161+
// Function to trigger the download of the raw data as a JSON file
162+
const downloadRawData = () => {
163+
const filename = "raw_region_summary.json";
164+
const jsonStr = JSON.stringify(fetchedInfo, null, 4);
165+
const element = document.createElement('a');
166+
element.setAttribute('href', 'data:text/json;charset=utf-8,' + encodeURIComponent(jsonStr));
167+
element.setAttribute('download', filename);
168+
element.style.display = 'none';
169+
document.body.appendChild(element);
170+
element.click();
171+
document.body.removeChild(element);
172+
};
173+
174+
return (
175+
<Panel style={{ marginTop: "10px" }}>
176+
<div>
177+
<div>
178+
<strong style={{ fontSize: "smaller" }}>Buildings:</strong>
179+
<p style={{ fontSize: "smaller", textAlign: "justify", margin: "10px 0" }}>
180+
<FormattedMessage
181+
id="export.dc.stats.buildings"
182+
defaultMessage="{buildings}"
183+
values={{ buildings: fetchedInfo.summary.buildings }}
184+
/>
185+
</p>
186+
</div>
187+
<div>
188+
<strong style={{ fontSize: "smaller" }}>Roads:</strong>
189+
<p style={{ fontSize: "smaller", textAlign: "justify", margin: "10px 0" }}>
190+
<FormattedMessage
191+
id="export.dc.stats.roads"
192+
defaultMessage="{roads}"
193+
values={{ roads: fetchedInfo.summary.roads }}
194+
/>
195+
</p>
196+
</div>
197+
<div style={{ fontSize: "smaller", marginTop: "10px" }}>
198+
More info:
199+
<a href="#" onClick={downloadRawData} style={{ marginLeft: "5px" }}>Download</a>,
200+
<a href={fetchedInfo.meta.indicators} target="_blank" rel="noopener noreferrer" style={{ marginLeft: "5px" }}>Indicators</a>,
201+
<a href={fetchedInfo.meta.metrics} target="_blank" rel="noopener noreferrer" style={{ marginLeft: "5px" }}>Metrics</a>
202+
</div>
203+
</div>
204+
</Panel>
205+
);
206+
}
207+
208+
209+
210+
131211
componentWillMount() {
132212
const { getConfigurations, getOverpassTimestamp, getGalaxyTimestamp} = this.props;
133213

@@ -281,10 +361,11 @@ export class ExportForm extends Component {
281361
/>}
282362
/>
283363
</Switch>
364+
{this.renderFetchedInfo()}
284365
<Panel style={{ marginTop: "20px" }}>
285366
<FormattedMessage
286367
id="ui.overpass_last_updated"
287-
defaultMessage="Img/pbf/obf/ updated {overpassLastUpdated}, Rest of other formats updated {galaxyLastUpdated} "
368+
defaultMessage="Img/pbf/obf updated {overpassLastUpdated}, Rest of other formats updated {galaxyLastUpdated} "
288369
values={{ overpassLastUpdated, galaxyLastUpdated }}
289370
/>
290371
</Panel>

ui/app/components/Message.js

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React, { Component } from "react";
2+
3+
class Message extends Component {
4+
constructor(props) {
5+
super(props);
6+
// Use "messageClosed" as the key to check if the message was previously closed
7+
const messageClosed = localStorage.getItem("messageClosed") === "true";
8+
this.state = {
9+
isVisible: !messageClosed,
10+
};
11+
}
12+
13+
handleClose = () => {
14+
this.setState({ isVisible: false });
15+
// When closing, set "messageClosed" in localStorage to "true"
16+
localStorage.setItem("messageClosed", "true");
17+
};
18+
19+
render() {
20+
if (!this.state.isVisible) {
21+
return null;
22+
}
23+
return (
24+
<div className="banner" style={{ backgroundColor: "#ffcc00", color: "black", textAlign: "center", padding: "10px", position: "relative" }}>
25+
<p>We have recently upgraded from OAuth 1.0 to 2.0. Please Logout and Login again before use!</p>
26+
<button onClick={this.handleClose} style={{ position: "absolute", top: "5px", right: "10px", cursor: "pointer" }}>
27+
×
28+
</button>
29+
</div>
30+
);
31+
}
32+
}
33+
34+
export default Message;

ui/app/components/utils.js

+5-5
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,11 @@ export const AVAILABLE_EXPORT_FORMATS = {
5858
SQL <code>.sql</code>
5959
</span>
6060
),
61+
mbtiles: (
62+
<span key="mbtiles">
63+
MBTiles <code>.mbtiles</code>
64+
</span>
65+
),
6166
garmin_img: (
6267
<span key="garmin_img">
6368
Garmin <code>.img</code>
@@ -83,11 +88,6 @@ export const AVAILABLE_EXPORT_FORMATS = {
8388
OsmAnd <code>.obf</code>
8489
</span>
8590
),
86-
mbtiles: (
87-
<span key="osmand_obf">
88-
MBTiles <code>.mbtiles</code>
89-
</span>
90-
),
9191
bundle: (
9292
<span key="bundle">
9393
<a href="http://posm.io/">POSM</a> bundle

ui/templates/ui/v3.html

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
<script>
3131
var EXPORTS_API_URL = "{{ request.scheme }}://{{ request.get_host }}";
3232
var OAUTH_CLIENT_ID = "{{ client_id }}";
33+
var RAW_DATA_API_URL = "{{ RAW_DATA_API_URL }}";
3334
</script>
3435
<script src="{% static 'ui/js/bundle.js' %}"></script>
3536
</body>

ui/views.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def v3(request, *args, **kwargs):
5656
skip_authorization=True,
5757
)
5858

59-
context = dict(client_id=ui_app.client_id)
59+
context = dict(
60+
client_id=ui_app.client_id, RAW_DATA_API_URL=settings.RAW_DATA_API_URL
61+
)
6062
if settings.MATOMO_URL is not None and settings.MATOMO_SITEID is not None:
6163
context.update(
6264
{"MATOMO_URL": settings.MATOMO_URL, "MATOMO_SITEID": settings.MATOMO_SITEID}

ui/webpack.config.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,8 @@ const config = {
9393
plugins: [new webpack.DefinePlugin({
9494
"process.env": {
9595
CLIENT_ID: JSON.stringify(process.env.CLIENT_ID),
96-
EXPORTS_API_URL: JSON.stringify(process.env.EXPORTS_API_URL)
96+
EXPORTS_API_URL: JSON.stringify(process.env.EXPORTS_API_URL),
97+
RAW_DATA_API_URL: JSON.stringify(process.env.RAW_DATA_API_URL)
9798
}
9899
}), new webpack.NamedModulesPlugin(), new WriteFilePlugin()],
99100
resolve: {
@@ -110,6 +111,7 @@ if (process.env.NODE_ENV === "production") {
110111
"process.env": {
111112
CLIENT_ID: JSON.stringify(process.env.CLIENT_ID),
112113
EXPORTS_API_URL: JSON.stringify(process.env.EXPORTS_API_URL),
114+
RAW_DATA_API_URL: JSON.stringify(process.env.RAW_DATA_API_URL),
113115
NODE_ENV: JSON.stringify("production")
114116
}
115117
}),

0 commit comments

Comments
 (0)