From 45ab65587d790d71f760cd5040257f8be11b23be Mon Sep 17 00:00:00 2001
From: Bertrand PINEL <bpinel@ippon.fr>
Date: Wed, 7 Oct 2020 14:42:31 +0200
Subject: [PATCH] Follow road using Mapbox WS API

---
 app/components/path-editor.js         | 128 +++++++++++++++-----------
 app/services/mapbox-ws.js             |  85 +++++++++++++++++
 config/environment.js                 |   1 +
 package-lock.json                     |  11 ++-
 package.json                          |   3 +
 tests/unit/services/mapbox-ws-test.js |  12 +++
 6 files changed, 184 insertions(+), 56 deletions(-)
 create mode 100644 app/services/mapbox-ws.js
 create mode 100644 tests/unit/services/mapbox-ws-test.js

diff --git a/app/components/path-editor.js b/app/components/path-editor.js
index e9e6238..60f725b 100644
--- a/app/components/path-editor.js
+++ b/app/components/path-editor.js
@@ -1,58 +1,78 @@
 import Component from '@glimmer/component';
-import { tracked } from '@glimmer/tracking';
-import { action } from '@ember/object';
+import {tracked} from '@glimmer/tracking';
+import {action} from '@ember/object';
 import mapboxgl from 'mapbox-gl';
- 
+
+import {inject as service} from '@ember/service';
+
+
 export default class PathEditorComponent extends Component {
- 
- @tracked lat = 0
- @tracked lng = 0
- @tracked polyline = []
- startPoint
- geoJsonPolyline = {
-   "geometry": {
-     "coordinates": this.polyline,
-     "type": "LineString"
-   },
-   "type": "Feature",
-   "properties": {
-     "name": "Cyclo path"
-   }
- }
- initLayer = false
-  
- @action
- async mapClicked({ target:map, point}) {
-   let clickCoord = map.unproject(point);
-   this.lat = clickCoord.lat;
-   this.lng = clickCoord.lng;
-   let newCoord = [this.lng, this.lat];
-   this.polyline.push(newCoord);
- 
-   if (this.polyline.length == 1) {
-     // First point special processing --> add a marker as starting point
-     this.startPoint = new mapboxgl.Marker()
-       .setLngLat([this.lng, this.lat]).addTo(map);
-   } else {
-       if (!this.initLayer) {
-         map.addSource('cycling-path', { 'type': 'geojson', 'data': this.geoJsonPolyline });
-         map.addLayer({
-           'id': 'Drawn_cycling_path',
-           'type': 'line',
-           'source': 'cycling-path',
-           'layout': {
-             'line-cap': 'round'
-           },
-           'paint': {
-               'line-dasharray': [1, 2],
-               'line-color': '#f77',
-               'line-width': 3
-           }
-       });
-         this.initLayer=true;
-       } else {
-         map.getSource('cycling-path').setData(this.geoJsonPolyline);
-       }
-   }
- }
+
+    @service mapboxWs;
+
+    @tracked lat = 0
+    @tracked lng = 0 
+    @tracked polyline = []
+    startPoint 
+    geoJsonPolyline = {
+        "geometry": {
+            "coordinates": this.polyline,
+            "type": "LineString"
+        },
+        "type": "Feature",
+        "properties": {
+            "name": "Cyclo path"
+        }
+    }
+    initLayer = false 
+    
+    @action async mapClicked({target: map, point}) {
+        let clickCoord = map.unproject(point);
+        this.lat = clickCoord.lat;
+        this.lng = clickCoord.lng;
+        let newCoord = [this.lng, this.lat];
+        this.polyline.push(newCoord);
+
+        if (this.polyline.length == 1) { // First point special processing --> add a marker as starting point
+            this.startPoint = new mapboxgl.Marker().setLngLat([this.lng, this.lat]).addTo(map);
+        } else {
+            /**** Addition for following roads */
+            // complete polyline by first retrieving new path though mapbox service
+            let previousCoord = this.polyline[this.polyline.length - 2];
+            let path = await this.mapboxWs.direction("cycling", previousCoord, newCoord);
+            if (path.length > 0) { // Insert intermediate coord in the polyline
+                let last = this.polyline.pop();
+                for (let i = 0; i < path.length; i++) {
+                    this.polyline.push(path[i]);
+                }
+            }
+            // Don't put back last point that could be outside the road
+            // this.polyline.push(last);
+            /** End of addition */
+            if (!this.initLayer) {
+                map.addSource('cycling-path', {
+                    'type': 'geojson',
+                    'data': this.geoJsonPolyline
+                });
+                map.addLayer({
+                    'id': 'Drawn_cycling_path',
+                    'type': 'line',
+                    'source': 'cycling-path',
+                    'layout': {
+                        'line-cap': 'round'
+                    },
+                    'paint': {
+                        'line-dasharray': [
+                            1, 2
+                        ],
+                        'line-color': '#f77',
+                        'line-width': 3
+                    }
+                });
+                this.initLayer = true;
+            } else {
+                map.getSource('cycling-path').setData(this.geoJsonPolyline);
+            }
+        }
+    }
 }
diff --git a/app/services/mapbox-ws.js b/app/services/mapbox-ws.js
new file mode 100644
index 0000000..ef7dafb
--- /dev/null
+++ b/app/services/mapbox-ws.js
@@ -0,0 +1,85 @@
+import Service from '@ember/service';
+import axios from 'axios';
+import config from '../config/environment';
+
+export default class MapboxWsService extends Service {
+  baseUrl = 'https://api.mapbox.com';
+
+  token = config.mapboxToken;
+
+  init() {
+    super.init(...arguments);
+
+    this.axios = axios.create(this.config());
+
+    this.load(this.axios);
+
+    this.request = this.axios.get;
+    this.get = this.axios.get;
+    this.delete = this.axios.delete;
+    this.head = this.axios.head;
+    this.options = this.axios.options;
+    this.post = this.axios.post;
+    this.put = this.axios.put;
+    this.patch = this.axios.patch;
+  }
+
+  client() {
+    return this.axios;
+  }
+
+  async direction(profile, coord1, coord2) {
+    const coords = coord1[0]+','+coord1[1]+';'+coord2[0]+','+coord2[1]
+    const urlOptimize = this.baseUrl+'/directions/v5/mapbox/'+profile+'/'+coords+'?geometries=geojson&access_token='+this.token;
+    let response = await axios({
+      method: 'get',
+      url: urlOptimize,
+      mode: 'no-cors',
+      headers: {
+        "Accept": "*/*",
+        "content-type": "text/plain",
+        "Access-Control-Allow-Origin": "*"
+    }
+    });
+    if (response.status === 200 && response.data.code === 'Ok') {
+        return response.data.routes[0].geometry.coordinates;
+    } else {
+        return [];
+    }   
+  }
+
+  async optimize(profile, coord1, coord2) {
+    const coords = coord1[0]+','+coord1[1]+';'+coord2[0]+','+coord2[1]
+    const urlOptimize = this.baseUrl+'/optimized-trips/v1/mapbox/'+profile+'/'+coords+'?geometries=geojson&access_token='+this.token;
+    let response = await axios({
+      method: 'get',
+      url: urlOptimize,
+      mode: 'no-cors',
+      headers: {
+        "Accept": "*/*",
+        "content-type": "text/plain",
+        "Access-Control-Allow-Origin": "*"
+    }
+    });
+    if (response.status === 200 && response.data.code === 'Ok') {
+        return response.data.trips[0].geometry.coordinates;
+    } else {
+        return [];
+    }
+  }
+  
+  headers() {
+    return {};
+  }
+
+  config() {
+    return {
+      baseURL: this.baseUrl,
+      headers: this.headers()
+    };
+  }
+
+  load() {
+    return true;
+  }
+}
\ No newline at end of file
diff --git a/config/environment.js b/config/environment.js
index c1263c3..2503a7d 100644
--- a/config/environment.js
+++ b/config/environment.js
@@ -6,6 +6,7 @@ module.exports = function(environment) {
     environment,
     rootURL: '/',
     locationType: 'auto',
+    mapboxToken: process.env.MAPBOX_TOKEN,
     'mapbox-gl': {
       accessToken: process.env.MAPBOX_TOKEN,
       map: {
diff --git a/package-lock.json b/package-lock.json
index 0f830c5..f196080 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3772,6 +3772,14 @@
       "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==",
       "dev": true
     },
+    "axios": {
+      "version": "0.20.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.20.0.tgz",
+      "integrity": "sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA==",
+      "requires": {
+        "follow-redirects": "^1.10.0"
+      }
+    },
     "babel-code-frame": {
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz",
@@ -12535,8 +12543,7 @@
     "follow-redirects": {
       "version": "1.13.0",
       "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
-      "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
-      "dev": true
+      "integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
     },
     "for-in": {
       "version": "1.0.2",
diff --git a/package.json b/package.json
index 30bc57f..b7da62e 100644
--- a/package.json
+++ b/package.json
@@ -57,5 +57,8 @@
   },
   "ember": {
     "edition": "octane"
+  },
+  "dependencies": {
+    "axios": "^0.20.0"
   }
 }
diff --git a/tests/unit/services/mapbox-ws-test.js b/tests/unit/services/mapbox-ws-test.js
new file mode 100644
index 0000000..2e1e4e4
--- /dev/null
+++ b/tests/unit/services/mapbox-ws-test.js
@@ -0,0 +1,12 @@
+import { module, test } from 'qunit';
+import { setupTest } from 'ember-qunit';
+
+module('Unit | Service | mapbox-ws', function(hooks) {
+  setupTest(hooks);
+
+  // TODO: Replace this with your real tests.
+  test('it exists', function(assert) {
+    let service = this.owner.lookup('service:mapbox-ws');
+    assert.ok(service);
+  });
+});
-- 
GitLab