API & Client: use of calculated bounds from API to display map
This commit is contained in:
		
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							@@ -53,7 +53,7 @@ test-python:
 | 
				
			|||||||
test-python-xml:
 | 
					test-python-xml:
 | 
				
			||||||
	$(PYTEST) mpwo_api --cov-config .coveragerc --cov=mpwo_api --cov-report xml
 | 
						$(PYTEST) mpwo_api --cov-config .coveragerc --cov=mpwo_api --cov-report xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
update-cov:
 | 
					update-cov:	test-python-xml
 | 
				
			||||||
	$(COV) -r coverage.xml
 | 
						$(COV) -r coverage.xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
upgrade-db:
 | 
					upgrade-db:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										28
									
								
								mpwo_api/migrations/versions/92adde6ac0d0_.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								mpwo_api/migrations/versions/92adde6ac0d0_.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					"""add 'bounds' column to 'Activity' table
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Revision ID: 92adde6ac0d0
 | 
				
			||||||
 | 
					Revises: dd73d23a7a3d
 | 
				
			||||||
 | 
					Create Date: 2018-05-14 14:25:04.889189
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					"""
 | 
				
			||||||
 | 
					from alembic import op
 | 
				
			||||||
 | 
					import sqlalchemy as sa
 | 
				
			||||||
 | 
					from sqlalchemy.dialects import postgresql
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# revision identifiers, used by Alembic.
 | 
				
			||||||
 | 
					revision = '92adde6ac0d0'
 | 
				
			||||||
 | 
					down_revision = 'dd73d23a7a3d'
 | 
				
			||||||
 | 
					branch_labels = None
 | 
				
			||||||
 | 
					depends_on = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def upgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.add_column('activities', sa.Column('bounds', postgresql.ARRAY(sa.Float()), nullable=True))
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def downgrade():
 | 
				
			||||||
 | 
					    # ### commands auto generated by Alembic - please adjust! ###
 | 
				
			||||||
 | 
					    op.drop_column('activities', 'bounds')
 | 
				
			||||||
 | 
					    # ### end Alembic commands ###
 | 
				
			||||||
@@ -1,6 +1,7 @@
 | 
				
			|||||||
import datetime
 | 
					import datetime
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from mpwo_api import db
 | 
					from mpwo_api import db
 | 
				
			||||||
 | 
					from sqlalchemy.dialects import postgresql
 | 
				
			||||||
from sqlalchemy.ext.hybrid import hybrid_property
 | 
					from sqlalchemy.ext.hybrid import hybrid_property
 | 
				
			||||||
from sqlalchemy.types import Enum
 | 
					from sqlalchemy.types import Enum
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -68,6 +69,7 @@ class Activity(db.Model):
 | 
				
			|||||||
    ascent = db.Column(db.Numeric(5, 2), nullable=True)      # meters
 | 
					    ascent = db.Column(db.Numeric(5, 2), nullable=True)      # meters
 | 
				
			||||||
    max_speed = db.Column(db.Numeric(5, 2), nullable=True)   # km/h
 | 
					    max_speed = db.Column(db.Numeric(5, 2), nullable=True)   # km/h
 | 
				
			||||||
    ave_speed = db.Column(db.Numeric(5, 2), nullable=True)   # km/h
 | 
					    ave_speed = db.Column(db.Numeric(5, 2), nullable=True)   # km/h
 | 
				
			||||||
 | 
					    bounds = db.Column(postgresql.ARRAY(db.Float), nullable=True)
 | 
				
			||||||
    segments = db.relationship('ActivitySegment',
 | 
					    segments = db.relationship('ActivitySegment',
 | 
				
			||||||
                               lazy=True,
 | 
					                               lazy=True,
 | 
				
			||||||
                               cascade='all, delete',
 | 
					                               cascade='all, delete',
 | 
				
			||||||
@@ -110,6 +112,7 @@ class Activity(db.Model):
 | 
				
			|||||||
            "max_speed": float(self.max_speed) if self.max_speed else None,
 | 
					            "max_speed": float(self.max_speed) if self.max_speed else None,
 | 
				
			||||||
            "ave_speed": float(self.ave_speed) if self.ave_speed else None,
 | 
					            "ave_speed": float(self.ave_speed) if self.ave_speed else None,
 | 
				
			||||||
            "with_gpx": self.gpx is not None,
 | 
					            "with_gpx": self.gpx is not None,
 | 
				
			||||||
 | 
					            "bounds": [float(bound) for bound in self.bounds] if self.bounds else [],  # noqa
 | 
				
			||||||
            "segments": [segment.serialize() for segment in self.segments]
 | 
					            "segments": [segment.serialize() for segment in self.segments]
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,7 @@ def create_activity(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if gpx_data:
 | 
					    if gpx_data:
 | 
				
			||||||
        new_activity.gpx = gpx_data['filename']
 | 
					        new_activity.gpx = gpx_data['filename']
 | 
				
			||||||
 | 
					        new_activity.bounds = gpx_data['bounds']
 | 
				
			||||||
        update_activity_data(new_activity, gpx_data)
 | 
					        update_activity_data(new_activity, gpx_data)
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        new_activity.moving = duration
 | 
					        new_activity.moving = duration
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,21 @@ def assert_activity_data_with_gpx(data):
 | 
				
			|||||||
    assert data['data']['activities'][0]['moving'] == '0:04:10'
 | 
					    assert data['data']['activities'][0]['moving'] == '0:04:10'
 | 
				
			||||||
    assert data['data']['activities'][0]['pauses'] is None
 | 
					    assert data['data']['activities'][0]['pauses'] is None
 | 
				
			||||||
    assert data['data']['activities'][0]['with_gpx'] is True
 | 
					    assert data['data']['activities'][0]['with_gpx'] is True
 | 
				
			||||||
 | 
					    assert len(data['data']['activities'][0]['segments']) == 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    segment = data['data']['activities'][0]['segments'][0]
 | 
				
			||||||
 | 
					    assert segment['activity_id'] == 1
 | 
				
			||||||
 | 
					    assert segment['segment_id'] == 0
 | 
				
			||||||
 | 
					    assert segment['duration'] == '0:04:10'
 | 
				
			||||||
 | 
					    assert segment['ascent'] == 0.4
 | 
				
			||||||
 | 
					    assert segment['ave_speed'] == 4.6
 | 
				
			||||||
 | 
					    assert segment['descent'] == 23.4
 | 
				
			||||||
 | 
					    assert segment['distance'] == 0.32
 | 
				
			||||||
 | 
					    assert segment['max_alt'] == 998.0
 | 
				
			||||||
 | 
					    assert segment['max_speed'] == 5.09
 | 
				
			||||||
 | 
					    assert segment['min_alt'] == 975.0
 | 
				
			||||||
 | 
					    assert segment['moving'] == '0:04:10'
 | 
				
			||||||
 | 
					    assert segment['pauses'] is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def assert_activity_data_wo_gpx(data):
 | 
					def assert_activity_data_wo_gpx(data):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,8 +23,12 @@ class ActivityMap extends React.Component {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  render() {
 | 
					  render() {
 | 
				
			||||||
    const { gpxContent } = this.props
 | 
					    const { activity, gpxContent } = this.props
 | 
				
			||||||
    const { jsonData, bounds } = getGeoJson(gpxContent)
 | 
					    const { jsonData } = getGeoJson(gpxContent)
 | 
				
			||||||
 | 
					    const bounds = [
 | 
				
			||||||
 | 
					      [activity.bounds[0], activity.bounds[1]],
 | 
				
			||||||
 | 
					      [activity.bounds[2], activity.bounds[3]]
 | 
				
			||||||
 | 
					    ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
      <div>
 | 
					      <div>
 | 
				
			||||||
@@ -32,7 +36,7 @@ class ActivityMap extends React.Component {
 | 
				
			|||||||
          <Map
 | 
					          <Map
 | 
				
			||||||
            zoom={this.state.zoom}
 | 
					            zoom={this.state.zoom}
 | 
				
			||||||
            bounds={bounds}
 | 
					            bounds={bounds}
 | 
				
			||||||
            boundsOptions={{ padding: [50, 50] }}
 | 
					            boundsOptions={{ padding: [20, 20] }}
 | 
				
			||||||
          >
 | 
					          >
 | 
				
			||||||
            <TileLayer
 | 
					            <TileLayer
 | 
				
			||||||
              // eslint-disable-next-line max-len
 | 
					              // eslint-disable-next-line max-len
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
import togeojson from '@mapbox/togeojson'
 | 
					import togeojson from '@mapbox/togeojson'
 | 
				
			||||||
import { format, parse } from 'date-fns'
 | 
					import { format, parse } from 'date-fns'
 | 
				
			||||||
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 = `${
 | 
				
			||||||
@@ -19,18 +18,12 @@ export const generateIds = arr => {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getGeoJson = gpxContent => {
 | 
					export const getGeoJson = gpxContent => {
 | 
				
			||||||
  let jsonData, bboxCorners
 | 
					  let jsonData
 | 
				
			||||||
  let bounds = [[0, 0], [0, 0]]
 | 
					 | 
				
			||||||
  if (gpxContent) {
 | 
					  if (gpxContent) {
 | 
				
			||||||
    const gpx = new DOMParser().parseFromString(gpxContent, 'text/xml')
 | 
					    const gpx = new DOMParser().parseFromString(gpxContent, 'text/xml')
 | 
				
			||||||
    jsonData = togeojson.gpx(gpx)
 | 
					    jsonData = togeojson.gpx(gpx)
 | 
				
			||||||
    bboxCorners = bbox(jsonData)
 | 
					 | 
				
			||||||
      bounds = [
 | 
					 | 
				
			||||||
      [bboxCorners[1], bboxCorners[0]],
 | 
					 | 
				
			||||||
      [bboxCorners[3], bboxCorners[2]]
 | 
					 | 
				
			||||||
    ]
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return { jsonData, bounds }
 | 
					  return { jsonData }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const formatActivityDate = activityDateTime => {
 | 
					export const formatActivityDate = activityDateTime => {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -4687,11 +4687,6 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
 | 
				
			||||||
      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
 | 
					      "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc="
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "geojson-bbox": {
 | 
					 | 
				
			||||||
      "version": "0.0.0",
 | 
					 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/geojson-bbox/-/geojson-bbox-0.0.0.tgz",
 | 
					 | 
				
			||||||
      "integrity": "sha1-6gf7YmEMzCmYeWAaVbqIQTm8G+g="
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    "get-caller-file": {
 | 
					    "get-caller-file": {
 | 
				
			||||||
      "version": "1.0.2",
 | 
					      "version": "1.0.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,6 @@
 | 
				
			|||||||
    "@mapbox/togeojson": "^0.16.0",
 | 
					    "@mapbox/togeojson": "^0.16.0",
 | 
				
			||||||
    "chalk": "2.4.1",
 | 
					    "chalk": "2.4.1",
 | 
				
			||||||
    "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.2",
 | 
					    "react": "^16.3.2",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3803,10 +3803,6 @@ generate-object-property@^1.1.0:
 | 
				
			|||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    is-property "^1.0.0"
 | 
					    is-property "^1.0.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"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user