Skip to content

Commit

Permalink
Replace main map with a Deck.gl layer
Browse files Browse the repository at this point in the history
Current state: no background, simple geometries are displayed. All
enabled layers are displayed, with proper color if that color is in the
properties, otherwise, it is a default color.

There is no interaction with the layers and the circles are very small
when zoomed out. The selected shaders do not work.

The original layer descriptions in the layers.config.ts file have not
been changed or updated yet. The information they contain may be useful
for the Deck.gl data, but we'll need to take each field and see how to
define/type it for best Deck.gl support
  • Loading branch information
tahini committed Nov 14, 2023
1 parent cd06053 commit a074c04
Show file tree
Hide file tree
Showing 9 changed files with 1,083 additions and 242 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -562,20 +562,20 @@ const defaultPreferences: PreferencesModel = {
maxTotalTravelTimeSeconds: 10800,
walkingSpeedMps: 1.3888888888,
walkingSpeedFactor: 1.0, // walking travel times are weighted using this factor: Example: > 1.0 means faster walking, < 1.0 means slower walking
originLocationColor: 'rgba(140, 212, 0, 1.0)',
destinationLocationColor: 'rgba(212, 35, 14, 1.0)',
walkingSegmentsColor: 'rgba(160,160,160,1.0)',
originLocationColor: '#8cd400',
destinationLocationColor: '#d4230e',
walkingSegmentsColor: '#a0a0a0',
walking: {
color: 'rgba(255, 238, 0,1.0)'
color: '#ffee00'
},
cycling: {
color: 'rgba(0, 204, 51,1.0)'
color: '#00cc33'
},
driving: {
color: 'rgba(229, 45, 0,1.0)'
color: '#e52d00'
},
default: {
color: 'rgba(160,160,160,1.0)'
color: '#a0a0a0'
}
},
transitAccessibilityMap: {
Expand All @@ -592,8 +592,8 @@ const defaultPreferences: PreferencesModel = {
walkingSpeedMps: 1.3888888888,
walkingSpeedFactor: 1.0, // walking travel times are weighted using this factor: Example: > 1.0 means faster walking, < 1.0 means slower walking
maxTotalTravelTimeSeconds: 1800,
locationColor: 'rgba(47, 138, 243, 1.0)',
polygonColor: 'rgba(47, 138, 243, 0.4)'
locationColor: '#2f8af3',
polygonColor: '#2f8af366'
},
transitOdTrips: {
minWaitingTimeSeconds: 180,
Expand Down
166 changes: 52 additions & 114 deletions packages/chaire-lib-frontend/src/services/map/MapLayerManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/
import MapboxGL from 'mapbox-gl';
import { featureCollection as turfFeatureCollection } from '@turf/turf';
import _uniq from 'lodash/uniq';

import serviceLocator from 'chaire-lib-common/lib/utils/ServiceLocator';
import { LayerDescription } from './layers/LayerDescription';

const defaultGeojson = turfFeatureCollection([]);
const defaultGeojson = turfFeatureCollection([]) as GeoJSON.FeatureCollection<GeoJSON.Geometry>;

/**
* Layer manager for Mapbox-gl maps
Expand All @@ -20,58 +20,37 @@ const defaultGeojson = turfFeatureCollection([]);
* TODO: If we want to support multiple map implementation, this layer management will have to be updated
*/
class MapboxLayerManager {
private _map: MapboxGL.Map | undefined;
private _layersByName: { [key: string]: any } = {};
private _layersByName: { [key: string]: LayerDescription } = {};
private _enabledLayers: string[] = [];
private _defaultFilterByLayer = {};
private _filtersByLayer = {};

constructor(layersConfig: any, map = undefined) {
this._map = map;

constructor(layersConfig: any) {
for (const layerName in layersConfig) {
const source = {
type: 'geojson',
data: defaultGeojson
};

const layer = layersConfig[layerName];
layer.id = layerName;
layer.source = layerName;

if (layersConfig[layerName].layout) {
layer.layout = layersConfig[layerName].layout;
}
if (layersConfig[layerName].defaultFilter) {
layer.filter = layersConfig[layerName].defaultFilter;
this._defaultFilterByLayer[layerName] = layersConfig[layerName].defaultFilter;
this._filtersByLayer[layerName] = layersConfig[layerName].defaultFilter;
} else {
this._filtersByLayer[layerName] = undefined;
this._defaultFilterByLayer[layerName] = undefined;
}

this._layersByName[layerName] = {
layer,
source
id: layerName,
visible: false,
layerDescription: layersConfig[layerName],
layerData: defaultGeojson
};
this._enabledLayers = [];
}
}

// TODO Consider deprecating and adding the map on the constructor only
setMap(map: MapboxGL.Map) {
this._map = map;
}

showLayer(layerName: string) {
this._map?.setLayoutProperty(layerName, 'visibility', 'visible');
if (this._layersByName[layerName] !== undefined) {
this._layersByName[layerName].visible = true;
}
}

hideLayer(layerName: string) {
this._map?.setLayoutProperty(layerName, 'visibility', 'none');
if (this._layersByName[layerName] !== undefined) {
this._layersByName[layerName].visible = false;
}
}

getFilter(layerName: string) {
// TODO Re-implement
return this._filtersByLayer[layerName] || null;
}

Expand All @@ -81,47 +60,24 @@ class MapboxLayerManager {
}
this._filtersByLayer[layerName] = filter;
if (this.layerIsEnabled(layerName)) {
this._map?.setFilter(layerName, this._filtersByLayer[layerName]);
// this._map?.setFilter(layerName, this._filtersByLayer[layerName]);
}
}

clearFilter(layerName: string) {
this._filtersByLayer[layerName] = this._defaultFilterByLayer[layerName];
if (this.layerIsEnabled(layerName)) {
this._map?.setFilter(layerName, this._defaultFilterByLayer[layerName]);
//this._map?.setFilter(layerName, this._defaultFilterByLayer[layerName]);
}
}

updateEnabledLayers(enabledLayers: string[] = []) {
if (!this._map) {
return;
}
enabledLayers = _uniq(enabledLayers); // make sure we do not have the same layer twice (can happen with user prefs not replaced correctly after updates)
const previousEnabledLayers: string[] = this._enabledLayers || [];
previousEnabledLayers.forEach((previousEnabledLayer) => {
this._map?.removeLayer(previousEnabledLayer); // we need to remove all layers so we can keep the right z-index: TODO: make this more efficient by recalculating z-index in mapbox order instead of reloading everything.
if (!enabledLayers.includes(previousEnabledLayer)) {
this._map?.removeSource(previousEnabledLayer);
}
});
const enabledAndActiveLayers: string[] = [];
enabledLayers.forEach((enabledLayer) => {
if (!this._layersByName[enabledLayer]) {
// Layer not defined
return;
}
if (!previousEnabledLayers.includes(enabledLayer)) {
this._map?.addSource(enabledLayer, this._layersByName[enabledLayer].source);
}
this._map?.addLayer(this._layersByName[enabledLayer].layer);
this._map?.setFilter(enabledLayer, this._filtersByLayer[enabledLayer]);
enabledAndActiveLayers.push(enabledLayer);
});
this._enabledLayers = enabledAndActiveLayers;
serviceLocator.eventManager.emit('map.updatedEnabledLayers', enabledLayers);
this._enabledLayers = _uniq(enabledLayers).filter((layerName) => this._layersByName[layerName] !== undefined); // make sure we do not have the same layer twice (can happen with user prefs not replaced correctly after updates)
serviceLocator.eventManager.emit('map.updatedEnabledLayers', this._enabledLayers);
}

showLayerObjectByAttribute(layerName: string, attribute: string, value: any) {
// TODO Reimplement
const existingFilter = this.getFilter(layerName);
let values: any[] = [];
if (
Expand All @@ -138,13 +94,15 @@ class MapboxLayerManager {
values = [value];
}
existingFilter[2] = values;
this._map?.setFilter(layerName, existingFilter);
// TODO Map needs updating at this point
// this._map?.setFilter(layerName, existingFilter);
} else {
this._map?.setFilter(layerName, ['match', ['get', attribute], values, true, false]);
// this._map?.setFilter(layerName, ['match', ['get', attribute], values, true, false]);
}
}

hideLayerObjectByAttribute(layerName, attribute, value) {
// TODO Reimplement
const existingFilter = this.getFilter(layerName);
let values: any[] = [];
if (
Expand All @@ -164,27 +122,28 @@ class MapboxLayerManager {
values = [value];
}
existingFilter[2] = values;
this._map?.setFilter(layerName, existingFilter);
// TODO Map needs updating at this point
// this._map?.setFilter(layerName, existingFilter);
} else {
this._map?.setFilter(layerName, ['match', ['get', attribute], values, true, false]);
// this._map?.setFilter(layerName, ['match', ['get', attribute], values, true, false]);
}
}

getLayer(layerName: string) {
return this._map?.getLayer(layerName);
return this._layersByName[layerName];
}

getNextLayerName(layerName: string) {
// to be able to add a layer before another (see mapbox map.addLayer attribute beforeId)
const enabledLayers = this._enabledLayers || [];
/* const enabledLayers = this._enabledLayers || [];
const enabledLayersCount = enabledLayers.length;
for (let i = 0; i < enabledLayersCount - 1; i++) {
// return undefined is already last (i < length - 1)
const enabledLayerName = this._enabledLayers[i];
if (enabledLayerName === layerName) {
return this._enabledLayers[i + 1];
}
}
} */
return undefined;
}

Expand All @@ -197,72 +156,51 @@ class MapboxLayerManager {
console.error('newShaderName is undefined or empty');
return null;
}
this._layersByName[layerName].layer['custom-shader'] = newShaderName;
// TODO Re-implement this
/* this._layersByName[layerName].layer['custom-shader'] = newShaderName;
this._map?.removeLayer(layerName);
if (this._map && repaint === true) {
this._map.repaint = true;
}
this._map?.addLayer(this._layersByName[layerName].layer, this.getNextLayerName(layerName));
this._map?.addLayer(this._layersByName[layerName].layer, this.getNextLayerName(layerName)); */
}

updateLayer(layerName, geojson) {
const newGeojson =
typeof geojson === 'function'
? geojson(this._layersByName[layerName].source.data)
: geojson
? geojson
: defaultGeojson;
this._layersByName[layerName].source.data = newGeojson;

if (this._map && this.layerIsEnabled(layerName)) {
(this._map.getSource(layerName) as MapboxGL.GeoJSONSource).setData(
this._layersByName[layerName].source.data
);
if (this._layersByName[layerName].layer.repaint === true) {
// activate repaint for animated shaders:
this._map.repaint = newGeojson.features && newGeojson.features.length > 0;
updateLayer(layerName: string, geojson: GeoJSON.FeatureCollection<GeoJSON.Geometry>) {
// FIXME: In original code, geojson can be a function. Do we need to support this? It took the source data as parameter
if (this._layersByName[layerName] !== undefined) {
this._layersByName[layerName].layerData = geojson;
if (this._layersByName[layerName].visible && this._enabledLayers.includes(layerName)) {
serviceLocator.eventManager.emit('map.updatedLayer', layerName);
}
} else {
console.log('layer does not exist', layerName);
}
serviceLocator.eventManager.emit('map.updatedLayer', layerName);
}

updateLayers(geojsonByLayerName) {
for (const layerName in geojsonByLayerName) {
const geojson = geojsonByLayerName[layerName];
const newGeojson =
typeof geojson === 'function'
? geojson(this._layersByName[layerName].source.data)
: geojson
? geojson
: defaultGeojson;
this._layersByName[layerName].source.data = newGeojson;
if (this._map && this.layerIsEnabled(layerName)) {
(this._map.getSource(layerName) as MapboxGL.GeoJSONSource).setData(
this._layersByName[layerName].source.data
);
if (this._layersByName[layerName].layer.repaint === true) {
// activate repaint for animated shaders:
this._map.repaint = newGeojson.features && newGeojson.features.length > 0;
if (this._layersByName[layerName] !== undefined) {
this._layersByName[layerName].layerData = geojsonByLayerName[layerName];
if (this._layersByName[layerName].visible && this._enabledLayers.includes(layerName)) {
serviceLocator.eventManager.emit('map.updatedLayer', layerName);
}
} else {
console.log('layer does not exist', layerName);
}
}
serviceLocator.eventManager.emit('map.updatedLayers', Object.keys(geojsonByLayerName));
}

layerIsEnabled(layerName: string) {
return this.getEnabledLayers().includes(layerName);
return this._enabledLayers.includes(layerName);
}

layerIsVisible(layerName: string) {
if (!this._map) {
return null;
}
const layerVisibility = this._map.getLayoutProperty(layerName, 'visibility');
return layerVisibility === 'visible' || layerVisibility === undefined;
return this._layersByName[layerName]?.visible;
}

getEnabledLayers() {
return this._enabledLayers;
getEnabledLayers(): LayerDescription[] {
return this._enabledLayers.map((layerName) => this._layersByName[layerName]);
}

getLayerNames() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright 2023, Polytechnique Montreal and contributors
*
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/

export type LayerDescription = {
/** Unique identifier for this layer */
id: string;
/** Whether the layer is visible, if enabled */
visible: boolean;
/**
* Description of the current layer
*
* TODO Type this properly
*/
layerDescription: { [key: string]: any };
/** Features contained in this layer */
layerData: GeoJSON.FeatureCollection<GeoJSON.Geometry>;
};
6 changes: 4 additions & 2 deletions packages/transition-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
},
"dependencies": {
"@alienfast/i18next-loader": "^1.1.4",
"@deck.gl/react": "^8.9.32",
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/react-fontawesome": "^0.1.11",
Expand All @@ -37,6 +38,7 @@
"chaire-lib-frontend": "^0.2.2",
"child_process": "^1.0.2",
"date-fns": "^2.23.0",
"deck.gl": "^8.9.32",
"element-resize-event": "^3.0.6",
"history": "^4.9.0",
"i18next": "^22.4.15",
Expand Down Expand Up @@ -113,10 +115,10 @@
"style-loader": "^1.2.1",
"ts-jest": "^27.0.7",
"ts-loader": "^7.0.5",
"ts-shader-loader": "^2.0.2",
"url-loader": "^4.1.0",
"webpack": "^4.43.0",
"webpack-cdn-plugin": "^3.3.1",
"webpack-cli": "^3.3.12",
"ts-shader-loader": "^2.0.2"
"webpack-cli": "^3.3.12"
}
}
Loading

0 comments on commit a074c04

Please sign in to comment.