Activities: display gpx on map
This commit is contained in:
parent
4d2f2e1afb
commit
a88a9a328c
@ -55,6 +55,61 @@ def get_activity(auth_user_id, activity_id):
|
|||||||
return jsonify(response_object), code
|
return jsonify(response_object), code
|
||||||
|
|
||||||
|
|
||||||
|
@activities_blueprint.route(
|
||||||
|
'/activities/<int:activity_id>/gpx',
|
||||||
|
methods=['GET']
|
||||||
|
)
|
||||||
|
@authenticate
|
||||||
|
def get_activity_gpx(auth_user_id, activity_id):
|
||||||
|
"""Get gpx file for an activity"""
|
||||||
|
activity = Activity.query.filter_by(id=activity_id).first()
|
||||||
|
gpx_content = ''
|
||||||
|
|
||||||
|
message = ''
|
||||||
|
code = 500
|
||||||
|
response_object = {
|
||||||
|
'status': 'error',
|
||||||
|
'message': 'internal error',
|
||||||
|
'data': {
|
||||||
|
'gpx': gpx_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if activity:
|
||||||
|
if not activity.gpx or activity.gpx == '':
|
||||||
|
response_object = {
|
||||||
|
'status': 'fail',
|
||||||
|
'message': 'No gpx file for this activity (id: {})'.format(
|
||||||
|
activity_id
|
||||||
|
)
|
||||||
|
}
|
||||||
|
code = 400
|
||||||
|
return jsonify(response_object), code
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(activity.gpx, encoding='utf-8') as f:
|
||||||
|
gpx_content = gpx_content + f.read()
|
||||||
|
except Exception as e:
|
||||||
|
appLog.error(e)
|
||||||
|
return jsonify(response_object), code
|
||||||
|
|
||||||
|
status = 'success'
|
||||||
|
code = 200
|
||||||
|
else:
|
||||||
|
status = 'not found'
|
||||||
|
message = 'Activity not found (id: {})'.format(activity_id)
|
||||||
|
code = 404
|
||||||
|
|
||||||
|
response_object = {
|
||||||
|
'status': status,
|
||||||
|
'message': message,
|
||||||
|
'data': {
|
||||||
|
'gpx': gpx_content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return jsonify(response_object), code
|
||||||
|
|
||||||
|
|
||||||
@activities_blueprint.route('/activities', methods=['POST'])
|
@activities_blueprint.route('/activities', methods=['POST'])
|
||||||
@authenticate
|
@authenticate
|
||||||
def post_activity(auth_user_id):
|
def post_activity(auth_user_id):
|
||||||
|
@ -2,6 +2,10 @@ import mpwoApi from '../mwpoApi/activities'
|
|||||||
import { history } from '../index'
|
import { history } from '../index'
|
||||||
import { setError } from './index'
|
import { setError } from './index'
|
||||||
|
|
||||||
|
export const setGpx = gpxContent => ({
|
||||||
|
type: 'SET_GPX',
|
||||||
|
gpxContent,
|
||||||
|
})
|
||||||
|
|
||||||
export function addActivity(form) {
|
export function addActivity(form) {
|
||||||
return function(dispatch) {
|
return function(dispatch) {
|
||||||
@ -17,3 +21,18 @@ export function addActivity(form) {
|
|||||||
.catch(error => dispatch(setError(`activities: ${error}`)))
|
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getActivityGpx(activityId) {
|
||||||
|
return function(dispatch) {
|
||||||
|
return mpwoApi
|
||||||
|
.getActivityGpx(activityId)
|
||||||
|
.then(ret => {
|
||||||
|
if (ret.status === 'success') {
|
||||||
|
dispatch(setGpx(ret.data.gpx))
|
||||||
|
} else {
|
||||||
|
dispatch(setError(`activities: ${ret.message}`))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => dispatch(setError(`activities: ${error}`)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -73,7 +73,7 @@ class ActivityDisplay extends React.Component {
|
|||||||
Map
|
Map
|
||||||
</div>
|
</div>
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<ActivityMap />
|
<ActivityMap activity={activity} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,30 +1,57 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Map, TileLayer } from 'react-leaflet'
|
import { GeoJSON, Map, TileLayer } from 'react-leaflet'
|
||||||
|
import { connect } from 'react-redux'
|
||||||
|
|
||||||
import { thunderforestApiKey } from '../../utils'
|
import { getActivityGpx } from '../../actions/activities'
|
||||||
|
import { getGeoJson, thunderforestApiKey } from '../../utils'
|
||||||
|
|
||||||
export default class ActivityMap extends React.Component {
|
class ActivityMap extends React.Component {
|
||||||
|
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context)
|
super(props, context)
|
||||||
this.state = {
|
this.state = {
|
||||||
lat: 51.505,
|
zoom: 13,
|
||||||
lng: -0.09,
|
|
||||||
zoom: 13,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.props.loadActivityGpx(this.props.activity.id)
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const position = [this.state.lat, this.state.lng]
|
const { gpxContent } = this.props
|
||||||
|
const { jsonData, bounds } = getGeoJson(gpxContent)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Map center={position} zoom={this.state.zoom}>
|
<div>
|
||||||
<TileLayer
|
{jsonData && (
|
||||||
// eslint-disable-next-line max-len
|
<Map
|
||||||
attribution='© <a href="http://www.thunderforest.com/">Thunderforest</a>, © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
zoom={this.state.zoom}
|
||||||
url="https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png"
|
bounds={bounds}
|
||||||
apikey={thunderforestApiKey}
|
boundsOptions={{ padding: [50, 50] }}
|
||||||
/>
|
>
|
||||||
</Map>
|
<TileLayer
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
attribution='© <a href="http://www.thunderforest.com/">Thunderforest</a>, © <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
|
||||||
|
// eslint-disable-next-line max-len
|
||||||
|
url={`https://{s}.tile.thunderforest.com/outdoors/{z}/{x}/{y}.png?apikey=${thunderforestApiKey}`}
|
||||||
|
/>
|
||||||
|
<GeoJSON data={jsonData} />
|
||||||
|
</Map>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
state => ({
|
||||||
|
gpxContent: state.gpx
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
loadActivityGpx: activityId => {
|
||||||
|
dispatch(getActivityGpx(activityId))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
)(ActivityMap)
|
||||||
|
@ -15,4 +15,16 @@ export default class MpwoApi {
|
|||||||
.catch(error => error)
|
.catch(error => error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getActivityGpx(activityId) {
|
||||||
|
const request = new Request(`${apiUrl}activities/${activityId}/gpx`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: new Headers({
|
||||||
|
Authorization: `Bearer ${window.localStorage.getItem('authToken')}`,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return fetch(request)
|
||||||
|
.then(response => response.json())
|
||||||
|
.catch(error => error)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,15 @@ const formProfile = (state = initial.formProfile, action) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const gpx = (state = initial.gpx, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case 'SET_GPX':
|
||||||
|
return action.gpxContent
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const message = (state = initial.message, action) => {
|
const message = (state = initial.message, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case 'AUTH_ERROR':
|
case 'AUTH_ERROR':
|
||||||
@ -144,6 +153,7 @@ const reducers = combineReducers({
|
|||||||
activities,
|
activities,
|
||||||
formData,
|
formData,
|
||||||
formProfile,
|
formProfile,
|
||||||
|
gpx,
|
||||||
message,
|
message,
|
||||||
messages,
|
messages,
|
||||||
router: routerReducer,
|
router: routerReducer,
|
||||||
|
@ -41,6 +41,8 @@ export default {
|
|||||||
activities: {
|
activities: {
|
||||||
...emptyData,
|
...emptyData,
|
||||||
},
|
},
|
||||||
|
// check if storing gpx content is OK
|
||||||
|
gpx: null,
|
||||||
sports: {
|
sports: {
|
||||||
...emptyData,
|
...emptyData,
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import togeojson from '@mapbox/togeojson'
|
||||||
|
import bbox from 'geojson-bbox'
|
||||||
|
|
||||||
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
|
export const apiUrl = `${process.env.REACT_APP_API_URL}/api/`
|
||||||
export const thunderforestApiKey = `${
|
export const thunderforestApiKey = `${
|
||||||
process.env.REACT_APP_THUNDERFOREST_API_KEY
|
process.env.REACT_APP_THUNDERFOREST_API_KEY
|
||||||
@ -13,3 +16,18 @@ export function generateIds(arr) {
|
|||||||
return obj
|
return obj
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getGeoJson(gpxContent) {
|
||||||
|
let jsonData, bboxCorners
|
||||||
|
let bounds = [[0, 0], [0, 0]]
|
||||||
|
if (gpxContent) {
|
||||||
|
const gpx = new DOMParser().parseFromString(gpxContent, 'text/xml')
|
||||||
|
jsonData = togeojson.gpx(gpx)
|
||||||
|
bboxCorners = bbox(jsonData)
|
||||||
|
bounds = [
|
||||||
|
[bboxCorners[1], bboxCorners[0]],
|
||||||
|
[bboxCorners[3], bboxCorners[2]]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return { jsonData, bounds }
|
||||||
|
}
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mapbox/togeojson": "^0.16.0",
|
||||||
"chalk": "2.3.0",
|
"chalk": "2.3.0",
|
||||||
"date-fns": "^1.29.0",
|
"date-fns": "^1.29.0",
|
||||||
|
"geojson-bbox": "^0.0.0",
|
||||||
"history": "^4.7.2",
|
"history": "^4.7.2",
|
||||||
"leaflet": "^1.3.1",
|
"leaflet": "^1.3.1",
|
||||||
"react": "^16.3.1",
|
"react": "^16.3.1",
|
||||||
|
39
yarn.lock
39
yarn.lock
@ -55,6 +55,14 @@
|
|||||||
lodash "^4.2.0"
|
lodash "^4.2.0"
|
||||||
to-fast-properties "^2.0.0"
|
to-fast-properties "^2.0.0"
|
||||||
|
|
||||||
|
"@mapbox/togeojson@^0.16.0":
|
||||||
|
version "0.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@mapbox/togeojson/-/togeojson-0.16.0.tgz#5b283001078431821dc74e287acaf56a11ceb37c"
|
||||||
|
dependencies:
|
||||||
|
concat-stream "~1.5.1"
|
||||||
|
minimist "1.2.0"
|
||||||
|
xmldom "~0.1.19"
|
||||||
|
|
||||||
"@types/chalk@^0.4.31":
|
"@types/chalk@^0.4.31":
|
||||||
version "0.4.31"
|
version "0.4.31"
|
||||||
resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"
|
resolved "https://registry.yarnpkg.com/@types/chalk/-/chalk-0.4.31.tgz#a31d74241a6b1edbb973cf36d97a2896834a51f9"
|
||||||
@ -1692,6 +1700,14 @@ concat-stream@^1.6.0:
|
|||||||
readable-stream "^2.2.2"
|
readable-stream "^2.2.2"
|
||||||
typedarray "^0.0.6"
|
typedarray "^0.0.6"
|
||||||
|
|
||||||
|
concat-stream@~1.5.1:
|
||||||
|
version "1.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
|
||||||
|
dependencies:
|
||||||
|
inherits "~2.0.1"
|
||||||
|
readable-stream "~2.0.0"
|
||||||
|
typedarray "~0.0.5"
|
||||||
|
|
||||||
configstore@^2.0.0:
|
configstore@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
|
resolved "https://registry.yarnpkg.com/configstore/-/configstore-2.1.0.tgz#737a3a7036e9886102aa6099e47bb33ab1aba1a1"
|
||||||
@ -3057,6 +3073,10 @@ gauge@~2.7.3:
|
|||||||
strip-ansi "^3.0.1"
|
strip-ansi "^3.0.1"
|
||||||
wide-align "^1.1.0"
|
wide-align "^1.1.0"
|
||||||
|
|
||||||
|
geojson-bbox@^0.0.0:
|
||||||
|
version "0.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/geojson-bbox/-/geojson-bbox-0.0.0.tgz#ea07fb62610ccc299879601a55ba884139bc1be8"
|
||||||
|
|
||||||
get-caller-file@^1.0.1:
|
get-caller-file@^1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5"
|
||||||
@ -4619,7 +4639,7 @@ minimist@0.0.8:
|
|||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
|
||||||
minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
|
minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||||
|
|
||||||
@ -5866,6 +5886,17 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable
|
|||||||
string_decoder "~1.0.3"
|
string_decoder "~1.0.3"
|
||||||
util-deprecate "~1.0.1"
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
|
readable-stream@~2.0.0:
|
||||||
|
version "2.0.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
|
||||||
|
dependencies:
|
||||||
|
core-util-is "~1.0.0"
|
||||||
|
inherits "~2.0.1"
|
||||||
|
isarray "~1.0.0"
|
||||||
|
process-nextick-args "~1.0.6"
|
||||||
|
string_decoder "~0.10.x"
|
||||||
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
readdirp@^2.0.0:
|
readdirp@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
|
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78"
|
||||||
@ -7003,7 +7034,7 @@ type-is@~1.6.15:
|
|||||||
media-typer "0.3.0"
|
media-typer "0.3.0"
|
||||||
mime-types "~2.1.15"
|
mime-types "~2.1.15"
|
||||||
|
|
||||||
typedarray@^0.0.6:
|
typedarray@^0.0.6, typedarray@~0.0.5:
|
||||||
version "0.0.6"
|
version "0.0.6"
|
||||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||||
|
|
||||||
@ -7472,6 +7503,10 @@ xml-name-validator@^2.0.1:
|
|||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
|
resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635"
|
||||||
|
|
||||||
|
xmldom@~0.1.19:
|
||||||
|
version "0.1.27"
|
||||||
|
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
|
||||||
|
|
||||||
xtend@^4.0.0, xtend@^4.0.1:
|
xtend@^4.0.0, xtend@^4.0.1:
|
||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
|
Loading…
Reference in New Issue
Block a user