diff --git a/front/index.js b/front/index.js
index bc96836..812ba0c 100644
--- a/front/index.js
+++ b/front/index.js
@@ -1,36 +1,76 @@
-const L = require('leaflet');
+require('ol/ol.css');
+
+const {Map, View} = require('ol');
+
+const TileLayer = require('ol/layer/Tile').default;
+const XYZSource = require('ol/source/XYZ').default;
+
+const VectorLayer = require('ol/layer/Vector').default;
+const VectorSource = require('ol/source/Vector').default;
+
+const Feature = require('ol/Feature').default;
+const Point = require('ol/geom/Point').default;
+const LineString = require('ol/geom/LineString').default;
+
+const proj = require('ol/proj');
+const {Style, Fill, Stroke, Circle} = require('ol/style');
+
const color = require('color');
-require('leaflet/dist/leaflet.css');
+// Map background
+const mapboxToken = `pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJjazUxaWNsdXcwdWhjM2\
+9tc2xndXJoNGtxIn0.xELwMerqJLFimIqU6RxnZw`;
-// MAP
-const map = L.map('map').setView(
- [43.605, 3.88],
- /* zoomLevel = */ 13
-);
+const backgroundSource = new XYZSource({
+ url: 'https://api.mapbox.com/' + [
+ 'styles', 'v1', 'mapbox', 'streets-v11',
+ 'tiles', '256', '{z}', '{x}', '{y}',
+ ].join('/') + `?access_token=${mapboxToken}`,
+});
-L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
- attribution: [
- // TODO: Attribution des données TaM
- `Données cartographiques © Contributeurs d’OpenStreetMap, CC-BY-SA`,
- 'Imagerie © Mapbox',
- ].join(' | '),
+const backgroundLayer = new TileLayer({
+ source: backgroundSource,
+});
- id: 'mapbox/streets-v11',
- tileSize: 512,
+// Data overlay
+const dataSource = new VectorSource();
- accessToken: 'pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJjazUxaWNsdXcwdWhjM29tc2xndXJoNGtxIn0.xELwMerqJLFimIqU6RxnZw',
+const SERVER = window.origin;
- maxZoom: 19,
- zoomSnap: 0,
- zoomOffset: -1,
-}).addTo(map);
+fetch(SERVER + '/network').then(res => res.json()).then(network =>
+{
+ const stopPoints = Object.entries(network.stops)
+ .map(([stopId, stop]) =>
+ {
+ const feature = new Feature({
+ type: 'stop',
+ color: network.lines[stop.lines[0]].color,
+ geometry: new Point(proj.fromLonLat([stop.lon, stop.lat])),
+ });
-//
-//
-// TAM
+ feature.setId(stopId);
+ return feature;
+ });
+
+ const segmentLines = Object.entries(network.segments)
+ .map(([segmentId, segment]) =>
+ {
+ const feature = new Feature({
+ type: 'segment',
+ color: network.lines[segment.lines[0]].color,
+ geometry: new LineString(segment.nodes.map(
+ ({lat, lon}) => proj.fromLonLat([lon, lat])
+ )),
+ })
+
+ feature.setId(segmentId);
+ return feature;
+ });
+
+ dataSource.addFeatures(
+ stopPoints.concat(segmentLines)
+ );
+});
const makeBorderColor = mainColor =>
{
@@ -39,41 +79,55 @@ const makeBorderColor = mainColor =>
return hsl.hex();
};
-const SERVER = window.origin;
-
-fetch(SERVER + '/network').then(res => res.json()).then(network =>
-{
- Object.values(network.segments).forEach(segment =>
+const dataLayer = new VectorLayer({
+ source: dataSource,
+ style: (feature, resolution) =>
{
- const color = network.lines[segment.lines[0]].color;
- const borderColor = makeBorderColor(color);
- const nodes = segment.nodes.map(({lat, lon}) => [lat, lon]);
-
- L.polyline(nodes, {weight: 8, color: borderColor}).addTo(map);
- const line = L.polyline(nodes, {weight: 6, color}).addTo(map);
-
- line.bindPopup(`${segment.length} m`);
- });
-
- Object.entries(network.stops).forEach(([stopId, stop]) =>
- {
- const color = network.lines[stop.lines[0]].color;
- const borderColor = makeBorderColor(color);
-
- const stopMarker = L.circleMarker(
- [stop.lat, stop.lon],
- {
- fillColor: color,
- radius: 6,
- fillOpacity: 1,
- color: borderColor,
- weight: 2
- }
- ).addTo(map);
-
- stopMarker.bindPopup(`${stop.name}
-Arrêt n°${stopId}`);
- });
-
- console.log(network);
+ if (feature.get('type') === 'stop')
+ {
+ return new Style({
+ image: new Circle({
+ fill: new Fill({
+ color: feature.get('color'),
+ }),
+ stroke: new Stroke({
+ color: makeBorderColor(feature.get('color')),
+ width: 2,
+ }),
+ radius: 6,
+ }),
+ });
+ }
+ else if (feature.get('type') === 'segment')
+ {
+ return [
+ new Style({
+ stroke: new Stroke({
+ color: makeBorderColor(feature.get('color')),
+ width: 8,
+ }),
+ }),
+ new Style({
+ stroke: new Stroke({
+ color: feature.get('color'),
+ width: 6,
+ }),
+ }),
+ ];
+ }
+ },
+});
+
+// Setup map
+const map = new Map({
+ target: 'map',
+ layers: [
+ backgroundLayer,
+ dataLayer,
+ ],
+ view: new View({
+ center: proj.fromLonLat([3.88, 43.605]),
+ zoom: 13,
+ maxZoom: 22,
+ }),
});
diff --git a/front/map/corridor.js b/front/map/corridor.js
new file mode 100644
index 0000000..f525ba9
--- /dev/null
+++ b/front/map/corridor.js
@@ -0,0 +1,71 @@
+/**
+ * Based on https://github.com/mikhailshilkov/leaflet-corridor
+ */
+
+const leaflet = require('leaflet');
+
+/**
+ * Polyline whose weight is based on a fixed number of meters of thickness
+ * rather than a static number of pixels.
+ */
+const Corridor = leaflet.Polyline.extend({
+ initialize(latlngs, options)
+ {
+ leaflet.Polyline.prototype.initialize.call(this, latlngs, options);
+
+ this.corridor = options.corridor;
+ },
+
+ onAdd(map)
+ {
+ leaflet.Polyline.prototype.onAdd.call(this, map);
+ map.on('zoomend', () => this._updateWeight(map));
+ this._updateWeight(map);
+ },
+
+ onRemove(map)
+ {
+ map.off('zoomend', () => this._updateWeight(map));
+ leaflet.Polyline.prototype.onRemove.call(this, map);
+ },
+
+ /**
+ * Update the actual line weight to span the requested number of meters
+ * based on the current meter density.
+ */
+ _updateWeight(map)
+ {
+ this.setStyle({
+ weight: this.corridor / this._getMetersPerPixel(map)
+ });
+ },
+
+ /**
+ * Calculate how many meters are in a pixel at the current zoom level
+ * around the current center point.
+ *
+ * @param map Map to take the zoom level from.
+ * @return Number of meters per pixel.
+ */
+ _getMetersPerPixel(map)
+ {
+ const centerLatLng = map.getCenter();
+ const centerPixels = map.latLngToContainerPoint(centerLatLng);
+
+ // Place two points left and right of the center
+ const point1 = leaflet.point(centerPixels.x + 5, centerPixels.y);
+ const point2 = leaflet.point(centerPixels.x - 5, centerPixels.y);
+
+ // Convert back to latitude and longitude then compute distance
+ const point1LatLng = map.containerPointToLatLng(point1);
+ const point2LatLng = map.containerPointToLatLng(point2);
+
+ console.log(point1LatLng.distanceTo(point2LatLng) / 10, 'mètres par pixel');
+ return point1LatLng.distanceTo(point2LatLng) / 10;
+ },
+});
+
+module.exports = (latlngs, options) => new Corridor(
+ latlngs,
+ options || { corridor: 100 }
+);
diff --git a/package-lock.json b/package-lock.json
index 17361dd..09971e0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -908,6 +908,11 @@
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz",
"integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw=="
},
+ "@openlayers/pepjs": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/@openlayers/pepjs/-/pepjs-0.5.3.tgz",
+ "integrity": "sha512-Bgvi5c14BS0FJWyYWWFstNEnXsB30nK8Jt8hkAAdqr7E0gDdBBWVDglF3Ub19wTxvgJ/CVHyTY6VuCtnyRzglg=="
+ },
"@parcel/fs": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-1.11.0.tgz",
@@ -5128,6 +5133,17 @@
"has": "^1.0.3"
}
},
+ "ol": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/ol/-/ol-6.1.1.tgz",
+ "integrity": "sha512-0dL3i3eJqgOpqIjDKEY3grkeQnjAYfV5L/JCxhOu4SxiaizRwFrFgeas6LILRoxKa03jhQFbut2r2bbgcLGQeA==",
+ "requires": {
+ "@openlayers/pepjs": "^0.5.3",
+ "pbf": "3.2.1",
+ "pixelworks": "1.1.0",
+ "rbush": "^3.0.1"
+ }
+ },
"on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -5393,6 +5409,15 @@
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
+ "pbf": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.2.1.tgz",
+ "integrity": "sha512-ClrV7pNOn7rtmoQVF4TS1vyU0WhYRnP92fzbfF75jAIwpnzdJXf8iTd4CMEqO4yUenH6NDqLiwjqlh6QgZzgLQ==",
+ "requires": {
+ "ieee754": "^1.1.12",
+ "resolve-protobuf-schema": "^2.1.0"
+ }
+ },
"pbkdf2": {
"version": "3.0.17",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz",
@@ -5427,6 +5452,11 @@
"integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
"dev": true
},
+ "pixelworks": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pixelworks/-/pixelworks-1.1.0.tgz",
+ "integrity": "sha1-Hwla1I3Ki/ihyCWOAJIDGkTyLKU="
+ },
"pn": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
@@ -5909,6 +5939,11 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
+ "protocol-buffers-schema": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.3.tgz",
+ "integrity": "sha512-jiszJ9Nzo8glRgP+PQ+QdQ/WqoolZLTIGBEG2PBwGWVGbTmq+j4S/3NR1kpoGE+pYXV2HfS8ukxXGKkBnMx7eA=="
+ },
"proxy-addr": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz",
@@ -6001,6 +6036,11 @@
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
},
+ "quickselect": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-2.0.0.tgz",
+ "integrity": "sha512-RKJ22hX8mHe3Y6wH/N3wCM6BWtjaxIyyUIkpHOvfFnxdI4yD4tBXEBKSbriGujF6jnSVkJrffuo6vxACiSSxIw=="
+ },
"quote-stream": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/quote-stream/-/quote-stream-1.0.2.tgz",
@@ -6044,6 +6084,14 @@
"unpipe": "1.0.0"
}
},
+ "rbush": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/rbush/-/rbush-3.0.1.tgz",
+ "integrity": "sha512-XRaVO0YecOpEuIvbhbpTrZgoiI6xBlz6hnlr6EHhd+0x9ase6EmeN+hdwwUaJvLcsFFQ8iWVF1GAK1yB0BWi0w==",
+ "requires": {
+ "quickselect": "^2.0.0"
+ }
+ },
"rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@@ -6270,6 +6318,14 @@
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz",
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g="
},
+ "resolve-protobuf-schema": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.1.0.tgz",
+ "integrity": "sha512-kI5ffTiZWmJaS/huM8wZfEMer1eRd7oJQhDuxeCLe3t7N7mX3z94CN0xPxBQxFYQTSNz9T0i+v6inKqSdK8xrQ==",
+ "requires": {
+ "protocol-buffers-schema": "^3.3.1"
+ }
+ },
"resolve-url": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
diff --git a/package.json b/package.json
index 0668b0b..0f622d1 100644
--- a/package.json
+++ b/package.json
@@ -16,6 +16,7 @@
"express": "^4.17.1",
"geolib": "^3.2.1",
"leaflet": "^1.6.0",
+ "ol": "^6.1.1",
"parcel-bundler": "^1.12.4",
"request": "^2.88.0",
"request-promise-native": "^1.0.8",