From 248d746a6765f091398f7f723b23fa10c5a03a2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Thu, 16 Jan 2020 00:22:23 +0100 Subject: [PATCH] Migrate to OpenLayers --- front/index.js | 176 +++++++++++++++++++++++++++--------------- front/map/corridor.js | 71 +++++++++++++++++ package-lock.json | 56 ++++++++++++++ package.json | 1 + 4 files changed, 243 insertions(+), 61 deletions(-) create mode 100644 front/map/corridor.js 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",