Display reports on the map
This commit is contained in:
parent
dd4075b18c
commit
e961a8dbb1
@ -3,7 +3,7 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = false
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
|
18
index.html
18
index.html
@ -1,12 +1,12 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<title>cyclassist</title>
|
<title>cyclassist</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<!-- built files will be auto injected -->
|
<!-- built files will be auto injected -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
160
package.json
160
package.json
@ -1,82 +1,82 @@
|
|||||||
{
|
{
|
||||||
"name": "cyclassist",
|
"name": "cyclassist",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A Vue.js project",
|
"description": "A Vue.js project",
|
||||||
"author": "Phyks (Lucas Verney) <phyks@phyks.me>",
|
"author": "Phyks (Lucas Verney) <phyks@phyks.me>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
|
||||||
"start": "npm run dev",
|
"start": "npm run dev",
|
||||||
"lint": "eslint --ext .js,.vue src",
|
"lint": "eslint --ext .js,.vue src",
|
||||||
"build": "node build/build.js"
|
"build": "node build/build.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es6-promise": "^4.2.4",
|
"es6-promise": "^4.2.4",
|
||||||
"isomorphic-fetch": "^2.2.1",
|
"isomorphic-fetch": "^2.2.1",
|
||||||
"leaflet": "^1.3.1",
|
"leaflet": "^1.3.1",
|
||||||
"leaflet-tracksymbol": "^1.0.8",
|
"leaflet-tracksymbol": "^1.0.8",
|
||||||
"material-icons": "^0.2.3",
|
"material-icons": "^0.2.3",
|
||||||
"nosleep.js": "^0.7.0",
|
"nosleep.js": "^0.7.0",
|
||||||
"roboto-fontface": "^0.9.0",
|
"roboto-fontface": "^0.9.0",
|
||||||
"vue": "^2.5.2",
|
"vue": "^2.5.2",
|
||||||
"vue-i18n": "^7.8.1",
|
"vue-i18n": "^7.8.1",
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vue2-leaflet": "^1.0.2",
|
"vue2-leaflet": "^1.0.2",
|
||||||
"vue2-leaflet-tracksymbol": "^1.0.10",
|
"vue2-leaflet-tracksymbol": "^1.0.10",
|
||||||
"vuetify": "^1.0.0"
|
"vuetify": "^1.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"autoprefixer": "^7.1.2",
|
"autoprefixer": "^7.1.2",
|
||||||
"babel-core": "^6.22.1",
|
"babel-core": "^6.22.1",
|
||||||
"babel-eslint": "^7.1.1",
|
"babel-eslint": "^7.1.1",
|
||||||
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
"babel-helper-vue-jsx-merge-props": "^2.0.3",
|
||||||
"babel-loader": "^7.1.1",
|
"babel-loader": "^7.1.1",
|
||||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
"babel-plugin-transform-runtime": "^6.22.0",
|
"babel-plugin-transform-runtime": "^6.22.0",
|
||||||
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
"babel-plugin-transform-vue-jsx": "^3.5.0",
|
||||||
"babel-preset-env": "^1.3.2",
|
"babel-preset-env": "^1.3.2",
|
||||||
"babel-preset-stage-2": "^6.22.0",
|
"babel-preset-stage-2": "^6.22.0",
|
||||||
"chalk": "^2.0.1",
|
"chalk": "^2.0.1",
|
||||||
"copy-webpack-plugin": "^4.0.1",
|
"copy-webpack-plugin": "^4.0.1",
|
||||||
"css-loader": "^0.28.0",
|
"css-loader": "^0.28.0",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
"eslint-config-airbnb-base": "^11.3.0",
|
"eslint-config-airbnb-base": "^11.3.0",
|
||||||
"eslint-friendly-formatter": "^3.0.0",
|
"eslint-friendly-formatter": "^3.0.0",
|
||||||
"eslint-import-resolver-webpack": "^0.8.3",
|
"eslint-import-resolver-webpack": "^0.8.3",
|
||||||
"eslint-loader": "^1.7.1",
|
"eslint-loader": "^1.7.1",
|
||||||
"eslint-plugin-html": "^3.0.0",
|
"eslint-plugin-html": "^3.0.0",
|
||||||
"eslint-plugin-import": "^2.7.0",
|
"eslint-plugin-import": "^2.7.0",
|
||||||
"extract-text-webpack-plugin": "^3.0.0",
|
"extract-text-webpack-plugin": "^3.0.0",
|
||||||
"file-loader": "^1.1.4",
|
"file-loader": "^1.1.4",
|
||||||
"friendly-errors-webpack-plugin": "^1.6.1",
|
"friendly-errors-webpack-plugin": "^1.6.1",
|
||||||
"html-webpack-plugin": "^2.30.1",
|
"html-webpack-plugin": "^2.30.1",
|
||||||
"node-notifier": "^5.1.2",
|
"node-notifier": "^5.1.2",
|
||||||
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
"optimize-css-assets-webpack-plugin": "^3.2.0",
|
||||||
"ora": "^1.2.0",
|
"ora": "^1.2.0",
|
||||||
"portfinder": "^1.0.13",
|
"portfinder": "^1.0.13",
|
||||||
"postcss-import": "^11.0.0",
|
"postcss-import": "^11.0.0",
|
||||||
"postcss-loader": "^2.0.8",
|
"postcss-loader": "^2.0.8",
|
||||||
"postcss-url": "^7.2.1",
|
"postcss-url": "^7.2.1",
|
||||||
"rimraf": "^2.6.0",
|
"rimraf": "^2.6.0",
|
||||||
"semver": "^5.3.0",
|
"semver": "^5.3.0",
|
||||||
"shelljs": "^0.7.6",
|
"shelljs": "^0.7.6",
|
||||||
"uglifyjs-webpack-plugin": "^1.1.1",
|
"uglifyjs-webpack-plugin": "^1.1.1",
|
||||||
"url-loader": "^0.5.8",
|
"url-loader": "^0.5.8",
|
||||||
"vue-loader": "^13.3.0",
|
"vue-loader": "^13.3.0",
|
||||||
"vue-style-loader": "^3.0.1",
|
"vue-style-loader": "^3.0.1",
|
||||||
"vue-template-compiler": "^2.5.2",
|
"vue-template-compiler": "^2.5.2",
|
||||||
"webpack": "^3.6.0",
|
"webpack": "^3.6.0",
|
||||||
"webpack-bundle-analyzer": "^2.9.0",
|
"webpack-bundle-analyzer": "^2.9.0",
|
||||||
"webpack-dev-server": "^2.9.1",
|
"webpack-dev-server": "^2.9.1",
|
||||||
"webpack-merge": "^4.1.0"
|
"webpack-merge": "^4.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6.0.0",
|
"node": ">= 6.0.0",
|
||||||
"npm": ">= 3.0.0"
|
"npm": ">= 3.0.0"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"> 1%",
|
"> 1%",
|
||||||
"last 2 versions",
|
"last 2 versions",
|
||||||
"not ie <= 8"
|
"not ie <= 8"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,13 @@ export function saveReport(type, lat, lng) {
|
|||||||
lat,
|
lat,
|
||||||
lng,
|
lng,
|
||||||
}),
|
}),
|
||||||
});
|
})
|
||||||
|
.catch(exc => console.error(`Unable to post report: ${exc}.`));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getReports() {
|
export function getReports() {
|
||||||
// TODO
|
return fetch(`${BASE_URL}api/v1/reports`)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(exc => console.error(`Unable to fetch reports: ${exc}.`));
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
<v-lmap :center="latlng" :zoom="this.zoom" :minZoom="this.minZoom" :maxZoom="this.maxZoom" :options="{ zoomControl: false }">
|
<v-lmap :center="latlng" :zoom="this.zoom" :minZoom="this.minZoom" :maxZoom="this.maxZoom" :options="{ zoomControl: false }">
|
||||||
<v-ltilelayer :url="tileServer" :attribution="attribution"></v-ltilelayer>
|
<v-ltilelayer :url="tileServer" :attribution="attribution"></v-ltilelayer>
|
||||||
<v-lts :lat-lng="latlng" :options="markerOptions"></v-lts>
|
<v-lts :lat-lng="latlng" :options="markerOptions"></v-lts>
|
||||||
|
<v-lmarker v-for="marker in markers" :key="marker.id" :lat-lng="marker.latLng"></v-lmarker>
|
||||||
</v-lmap>
|
</v-lmap>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -33,6 +34,7 @@ export default {
|
|||||||
heading: Number,
|
heading: Number,
|
||||||
lat: Number,
|
lat: Number,
|
||||||
lng: Number,
|
lng: Number,
|
||||||
|
markers: Array,
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
latlng() {
|
latlng() {
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
export const MOCK_LOCATION = { lat: 48.866667, lng: 2.333333, heading: 20 * (Math.PI / 180) };
|
|
||||||
// export const MOCK_LOCATION = false;
|
|
30
src/tools/index.js
Normal file
30
src/tools/index.js
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export function distance(latLng1, latLng2) {
|
||||||
|
const lat1 = (latLng1[0] * Math.PI) / 180;
|
||||||
|
const lng1 = (latLng1[1] * Math.PI) / 180;
|
||||||
|
|
||||||
|
const lat2 = (latLng2[0] * Math.PI) / 180;
|
||||||
|
const lng2 = (latLng2[1] * Math.PI) / 180;
|
||||||
|
|
||||||
|
const a = (
|
||||||
|
(Math.sin((lat2 - lat1) / 2.0) ** 2) +
|
||||||
|
(Math.cos(lat1) * Math.cos(lat2) * (Math.sin((lng2 - lng1) / 2.0) ** 2))
|
||||||
|
);
|
||||||
|
const c = 2.0 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
const EARTH_RADIUS = 6371000;
|
||||||
|
|
||||||
|
return EARTH_RADIUS * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mockLocation() {
|
||||||
|
const LAT_MIN = 48.854031;
|
||||||
|
const LNG_MIN = 2.281279;
|
||||||
|
const LAT_MAX = 48.886123;
|
||||||
|
const LNG_MAX = 2.392742;
|
||||||
|
return {
|
||||||
|
coords: {
|
||||||
|
latitude: (Math.random() * (LAT_MAX - LAT_MIN)) + LAT_MIN,
|
||||||
|
longitude: (Math.random() * (LNG_MAX - LNG_MIN)) + LNG_MIN,
|
||||||
|
heading: 20 * (Math.PI / 180),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
<v-container fluid fill-height class="no-padding">
|
<v-container fluid fill-height class="no-padding">
|
||||||
<v-layout row wrap fill-height>
|
<v-layout row wrap fill-height>
|
||||||
<v-flex xs12 fill-height v-if="lat && lng">
|
<v-flex xs12 fill-height v-if="lat && lng">
|
||||||
<Map :lat="lat" :lng="lng" :heading="heading"></Map>
|
<Map :lat="lat" :lng="lng" :heading="heading" :markers="reportsMarkers"></Map>
|
||||||
<v-btn
|
<v-btn
|
||||||
fixed
|
fixed
|
||||||
dark
|
dark
|
||||||
@ -30,9 +30,14 @@
|
|||||||
<script>
|
<script>
|
||||||
import NoSleep from 'nosleep.js';
|
import NoSleep from 'nosleep.js';
|
||||||
|
|
||||||
import { MOCK_LOCATION } from '@/constants';
|
import * as api from '@/api';
|
||||||
import Map from '@/components/Map.vue';
|
import Map from '@/components/Map.vue';
|
||||||
import ReportDialog from '@/components/ReportDialog/index.vue';
|
import ReportDialog from '@/components/ReportDialog/index.vue';
|
||||||
|
import { distance, mockLocation } from '@/tools';
|
||||||
|
|
||||||
|
const MOCK_LOCATION = false;
|
||||||
|
const MOCK_LOCATION_UPDATE_INTERVAL = 30 * 1000;
|
||||||
|
const UPDATE_REPORTS_DISTANCE_THRESHOLD = 500;
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -42,11 +47,23 @@ export default {
|
|||||||
created() {
|
created() {
|
||||||
this.initializePositionWatching();
|
this.initializePositionWatching();
|
||||||
this.setNoSleep();
|
this.setNoSleep();
|
||||||
|
this.fetchReports();
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.disableNoSleep();
|
this.disableNoSleep();
|
||||||
this.disablePositionWatching();
|
this.disablePositionWatching();
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
reportsMarkers() {
|
||||||
|
if (!this.reports) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return this.reports.map(report => ({
|
||||||
|
id: report.id,
|
||||||
|
latLng: [report.attributes.lat, report.attributes.lng],
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dialog: false,
|
dialog: false,
|
||||||
@ -55,6 +72,7 @@ export default {
|
|||||||
lat: null,
|
lat: null,
|
||||||
lng: null,
|
lng: null,
|
||||||
noSleep: null,
|
noSleep: null,
|
||||||
|
reports: null,
|
||||||
watchID: null,
|
watchID: null,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -62,15 +80,17 @@ export default {
|
|||||||
initializePositionWatching() {
|
initializePositionWatching() {
|
||||||
this.disablePositionWatching(); // Ensure at most one at the same time
|
this.disablePositionWatching(); // Ensure at most one at the same time
|
||||||
|
|
||||||
if (!('geolocation' in navigator)) {
|
|
||||||
this.error = this.$t('geolocation.unavailable');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MOCK_LOCATION) {
|
if (MOCK_LOCATION) {
|
||||||
this.lat = MOCK_LOCATION.lat;
|
this.setPosition(mockLocation());
|
||||||
this.lng = MOCK_LOCATION.lng;
|
this.watchID = setInterval(
|
||||||
this.heading = MOCK_LOCATION.heading;
|
() => this.setPosition(mockLocation()),
|
||||||
|
MOCK_LOCATION_UPDATE_INTERVAL,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
|
if (!('geolocation' in navigator)) {
|
||||||
|
this.error = this.$t('geolocation.unavailable');
|
||||||
|
}
|
||||||
|
|
||||||
this.watchID = navigator.geolocation.watchPosition(
|
this.watchID = navigator.geolocation.watchPosition(
|
||||||
this.setPosition,
|
this.setPosition,
|
||||||
this.handlePositionError,
|
this.handlePositionError,
|
||||||
@ -84,13 +104,26 @@ export default {
|
|||||||
},
|
},
|
||||||
disablePositionWatching() {
|
disablePositionWatching() {
|
||||||
if (this.watchID !== null) {
|
if (this.watchID !== null) {
|
||||||
navigator.geolocation.clearWatch(this.watchID);
|
if (MOCK_LOCATION) {
|
||||||
|
clearInterval(this.watchID);
|
||||||
|
} else {
|
||||||
|
navigator.geolocation.clearWatch(this.watchID);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handlePositionError(error) {
|
handlePositionError(error) {
|
||||||
this.error = `Error ${error.code}: ${error.message}`;
|
this.error = `Error ${error.code}: ${error.message}`;
|
||||||
},
|
},
|
||||||
setPosition(position) {
|
setPosition(position) {
|
||||||
|
if (this.lat && this.lng) {
|
||||||
|
const distanceFromPreviousPoint = distance(
|
||||||
|
[this.lat, this.lng],
|
||||||
|
[position.coords.latitude, position.coords.longitude],
|
||||||
|
);
|
||||||
|
if (distanceFromPreviousPoint > UPDATE_REPORTS_DISTANCE_THRESHOLD) {
|
||||||
|
this.fetchReports();
|
||||||
|
}
|
||||||
|
}
|
||||||
this.lat = position.coords.latitude;
|
this.lat = position.coords.latitude;
|
||||||
this.lng = position.coords.longitude;
|
this.lng = position.coords.longitude;
|
||||||
if (position.coords.heading) {
|
if (position.coords.heading) {
|
||||||
@ -101,7 +134,6 @@ export default {
|
|||||||
},
|
},
|
||||||
setNoSleep() {
|
setNoSleep() {
|
||||||
this.noSleep = new NoSleep();
|
this.noSleep = new NoSleep();
|
||||||
console.log(this.noSleep);
|
|
||||||
this.noSleep.enable();
|
this.noSleep.enable();
|
||||||
},
|
},
|
||||||
disableNoSleep() {
|
disableNoSleep() {
|
||||||
@ -109,6 +141,11 @@ export default {
|
|||||||
this.noSleep.disable();
|
this.noSleep.disable();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
fetchReports() {
|
||||||
|
api.getReports().then((reports) => {
|
||||||
|
this.reports = reports;
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
Loading…
Reference in New Issue
Block a user