Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate the main map to Deck.gl #773

Draft
wants to merge 23 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
b361dac
Deck.gl proof of work
florencelauer Apr 18, 2023
cab1aef
Replace main map with a Deck.gl layer
tahini Nov 10, 2023
fdae15d
Add MapLibre as the base map
davidmurray Nov 15, 2023
8f6d94d
Allow showing an XYZ tile layer
davidmurray Nov 15, 2023
e0fb537
Allow changing map background via the preferences
davidmurray Oct 27, 2023
c82a285
Remove references to mapbox in .env file
davidmurray Nov 15, 2023
55c230e
deck.gl: Change types of the map layer
tahini Nov 15, 2023
2cc3086
Make AnimatedArrowPathLayer work
davidmurray Nov 15, 2023
adc0f5b
LineLayer's should just be PathLayer, not TripsLayer
davidmurray Nov 16, 2023
d3320ac
deck.gl: Support events on map and map layers
tahini Nov 15, 2023
09371d3
deck.gl: Save zoom and center to user preferences
tahini Dec 4, 2023
21eeb0a
deck.gl: Add an updateCount to trigger map updates on layers
tahini Dec 18, 2023
1ca5d7c
deck.gl: Set styles for circle layers
tahini Dec 18, 2023
fa2fca6
deck.gl: Add comments and fix direction of animated path
tahini Jan 10, 2024
7a27614
deck.gl: Set styles for line layers
tahini Jan 9, 2024
31b7eb9
deck.gl: Set styles for polygon layers
tahini Jan 12, 2024
bac4147
deck.gl: Remove the original proof of concept
tahini Jan 17, 2024
a9762e4
deck.gl: Improve performance and look of animated path layer
kaligrafy Jan 18, 2024
9d8bdfe
deck.gl: Support filtering of features based on zoom
tahini Jan 19, 2024
073b7bb
deck.gl: Support filtering lines and agencies
tahini Jan 19, 2024
65b9bba
deck.gl: Remove dependencies to mapbox
tahini Jan 23, 2024
e451f59
preferences: Add an option to enable map animations
tahini Mar 11, 2024
3a8bd2b
animated path: Allow to disable animations for the layer
tahini Mar 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .env.docker
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ EXPRESS_SESSION_SECRET_KEY=DefaultDockerSessionKey
# Host and port for the trRouting server
#TR_ROUTING_HOST_URL=http://locahost
#TR_ROUTING_HOST_PORT=4000
# Required for the mapbox map
MAPBOX_ACCESS_TOKEN=MYMAPBOXACCESSTOKEN
MAPBOX_USER_ID=mapbox
MAPBOX_STYLE_ID=dark-v10
#MAGIC_LINK_SECRET_KEY=MYVERYLONGSECRETKEYTOENCRYPTTOKENTOSENDTOUSERFORPASSWORDLESSLOGIN
#CUSTOM_RASTER_TILES_XYZ_URL=https://exampltest/{z}/{x}/{y}
#CUSTOM_RASTER_TILES_MIN_ZOOM=8
Expand Down
4 changes: 0 additions & 4 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,6 @@ GOOGLE_OAUTH_SECRET_KEY=GOOGLEOAUTHSECRETKEY
# To support Facebook login
FACEBOOK_APP_ID=FACEBOOKAPPID
FACEBOOK_APP_SECRET=FACEBOOKAPPSECRET
# Required for the mapbox map
MAPBOX_ACCESS_TOKEN=MYMAPBOXACCESSTOKEN
MAPBOX_USER_ID=mapbox
MAPBOX_STYLE_ID=dark-v10
#MAGIC_LINK_SECRET_KEY=MYVERYLONGSECRETKEYTOENCRYPTTOKENTOSENDTOUSERFORPASSWORDLESSLOGIN
#CUSTOM_RASTER_TILES_XYZ_URL=https://exampltest/{z}/{x}/{y}
#CUSTOM_RASTER_TILES_MIN_ZOOM=8
Expand Down
9 changes: 8 additions & 1 deletion locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,17 @@
"General": "General preferences",
"DefaultSection": "Default section at opening",
"InfoPanelPosition": "Info panel position",
"MapStyle": "Map background",
"DefaultColor": "Default color",
"DefaultWalkingSpeedKph": "Default walking speed (km/h)",
"DefaultWalkingSpeedKphHelp": "Walking speed varies according to age and gender between 3 km/h (elderly people and young children) and 7 km/h (young and/or very active people). Men are on average a little faster than women and in general, walking speed decreases with age. The speed of a person in a manual wheelchair varies between 2 and 3 km/h depending on physical strength.",
"ExperimentalFeatures": "Experimental features",
"MapPrettyDisplay": "Display pretty lines and nodes on map",
"mapStyles": {
"osmBright": "OpenStreetMap",
"positron": "Light (positron)",
"darkMatter": "Dark (dark matter)"
},
"transit": {
"nodes": {}
},
Expand All @@ -279,7 +285,8 @@
},
"DateTimeFormat": "Date/Time format",
"DateTimeFormat24H": "24 hours ({{formatted}})",
"DateTimeFormat12H": "12 hours ({{formatted}})"
"DateTimeFormat12H": "12 hours ({{formatted}})",
"EnableMapAnimations": "Enable map animations"
},
"ShowingNofX": "Showing {{n}} of {{x}} results",
"PageNofX": "Page {{n}} of {{x}}",
Expand Down
9 changes: 8 additions & 1 deletion locales/fr/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,17 @@
"General": "Préférences générales",
"DefaultSection": "Section par défaut lors de l'ouverture",
"InfoPanelPosition": "Position du panneau d'information",
"MapStyle": "Fond de carte",
"DefaultColor": "Couleur par défaut",
"DefaultWalkingSpeedKph": "Vitesse de marche par défaut (km/h)",
"DefaultWalkingSpeedKphHelp": "La vitesse de marche varie selon l'âge et le genre entre 3 km/h (personnes âges et jeunes enfants) et 7 km/h (personnes jeunes et/ou très actives). Les hommes sont en moyenne un peu plus rapides que les femmes et de manière générale, la vitesse de marche diminue avec l'âge. La vitesse d'une personne en chaise roulante manuelle varie entre 2 et 3 km/h selon la force physique.",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

personnes âges -> personnes âgées

"ExperimentalFeatures": "Fonctionnalités expérimentales",
"MapPrettyDisplay": "Affichage esthétique des lignes et noeuds sur la carte",
"mapStyles": {
"osmBright": "OpenStreetMap",
"positron": "Clair (positron)",
"darkMatter": "Foncé (dark matter)"
},
"transit": {
"nodes": {}
},
Expand All @@ -279,7 +285,8 @@
},
"DateTimeFormat": "Format de date/heure",
"DateTimeFormat24H": "24 heures ({{formatted}})",
"DateTimeFormat12H": "12 heures ({{formatted}})"
"DateTimeFormat12H": "12 heures ({{formatted}})",
"EnableMapAnimations": "Activer les animations de cartes"
},
"ShowingNofX": "{{n}} de {{x}} résultats",
"PageNofX": "Page {{n}} de {{x}}",
Expand Down
21 changes: 12 additions & 9 deletions packages/chaire-lib-common/src/config/defaultPreferences.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface PreferencesModel {
defaultSection: string;
infoPanelPosition: string;
dateTimeFormat: string;
mapStyleURL: string;
sections: {
[key: string]: {
[key: string]: SectionDescription;
Expand All @@ -37,6 +38,7 @@ const defaultPreferences: PreferencesModel = {
defaultSection: 'agencies',
infoPanelPosition: 'right',
dateTimeFormat: 'YYYY-MM-DD HH:mm',
mapStyleURL: 'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json',
sections: {
transition: {
agencies: {
Expand Down Expand Up @@ -100,6 +102,7 @@ const defaultPreferences: PreferencesModel = {
map: {
center: [config.mapDefaultCenter.lon, config.mapDefaultCenter.lat],
zoom: 10,
enableMapAnimations: true,
layers: {
simulations: ['aggregatedOD', 'odTripsProfile', 'transitStations', 'transitNodes'],
agencies: [
Expand Down Expand Up @@ -564,20 +567,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 @@ -594,8 +597,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
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,5 @@ export const getPointCoordinates = (geojson: GeoJSON.GeoJSON): number[] | undefi
}
return undefined;
};

export const emptyFeatureCollection = { type: 'FeatureCollection', features: [] } as GeoJSON.FeatureCollection;
7 changes: 4 additions & 3 deletions packages/chaire-lib-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
"@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-solid-svg-icons": "^5.15.2",
"@fortawesome/react-fontawesome": "^0.1.11",
"@mapbox/mapbox-gl-draw": "^1.3.0",
"@turf/helpers": "^6.1.4",
"@turf/length": "^6.0.2",
"@turf/turf": "^6.3.0",
Expand All @@ -38,6 +37,7 @@
"chaire-lib-common": "^0.2.2",
"child_process": "^1.0.2",
"date-fns": "^2.30.0",
"deck.gl": "^8.9.32",
"font-awesome": "^4.7.0",
"geojson": "^0.5.0",
"history": "^4.9.0",
Expand All @@ -46,7 +46,7 @@
"i18next-http-backend": "^2.1.0",
"json-loader": "^0.5.7",
"lodash": "^4.17.21",
"mapbox-gl": "chairemobilite/mapbox-gl-js#39bbf9aeb1859424e29cff2584715fddc9d018b9",
"mjolnir.js": "^2.7.0",
"moment": "^2.29.4",
"moment-business-days": "^1.2.0",
"papaparse": "^5.3.1",
Expand Down Expand Up @@ -79,7 +79,6 @@
"@types/geojson": "^7946.0.7",
"@types/jest": "^26.0.4",
"@types/lodash": "^4.14.198",
"@types/mapbox-gl": "^2.3.1",
"@types/osrm": "^5.22.0",
"@types/react-loadable": "^5.5.6",
"@types/react-redux": "^7.1.9",
Expand All @@ -91,6 +90,7 @@
"@typescript-eslint/parser": "^5.45.1",
"copyfiles": "^2.4.1",
"cross-env": "^7.0.2",
"deck.gl": "^8.9.32",
"dotenv": "^8.2.0",
"enzyme": "^3.11.0",
"enzyme-adapter-react-16": "^1.15.4",
Expand All @@ -101,6 +101,7 @@
"jest": "^27.3.1",
"jest-each": "^27.3.1",
"jest-fetch-mock": "^3.0.3",
"mjolnir.js": "^2.7.0",
"mockdate": "^3.0.2",
"prettier-eslint-cli": "^7.1.0",
"react-select-event": "^5.0.0",
Expand Down
153 changes: 108 additions & 45 deletions packages/chaire-lib-frontend/src/services/map/IMapEventHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,51 +4,114 @@
* This file is licensed under the MIT License.
* License text available at https://opensource.org/licenses/MIT
*/
import MapboxGL from 'mapbox-gl';
import { MjolnirEvent } from 'mjolnir.js';
import { PickingInfo, Deck } from 'deck.gl/typed';

// TODO: Make this independent of mapbox eventually. For now, we use mapbox
// types, as they are already defined, when we have more implementations, we can
// review this
type MapEventHandler = (e: MapboxGL.MapMouseEvent) => void;
type MapLayerEventHandler = (e: MapboxGL.MapLayerMouseEvent & MapboxGL.EventData) => void;
export type MapCallbacks = { pickMultipleObjects: typeof Deck.prototype.pickMultipleObjects };
export type PointInfo = { coordinates: number[]; pixel: [number, number] };
export type MapEventHandler = (pointInfo: PointInfo, e: MjolnirEvent, mapCallbacks: MapCallbacks) => void;
export type MapSelectEventHandler = (pickInfo: PickingInfo[], e: MjolnirEvent, mapCallbacks: MapCallbacks) => void;
export type MapLayerEventHandler = (pickInfo: PickingInfo, e: MjolnirEvent, mapCallbacks: MapCallbacks) => void;
export type TooltipEventHandler = (
pickInfo: PickingInfo,
mapCallbacks: MapCallbacks
) => string | undefined | { text: string; containsHtml: boolean };

export type layerEventNames = 'onLeftClick' | 'onRightClick' | 'onDragStart' | 'onDrag' | 'onDragEnd' | 'onHover';
export type tooltipEventNames = 'onTooltip';
export type mapEventNames = 'onLeftClick' | 'onRightClick';

export type MapEventHandlerDescriptor = {
/** Type for handlers that only require the layer to be active */
type: 'map';
eventName: mapEventNames;
/**
* Condition function for which this handler applies. It will be checked
* before actually calling the handler, so the handler can assume this is
* true.
*
* TODO: This should depend on some application's state or context, that
* should be passed here. For now, we pass the active section and the rest
* can be accessed through the serviceLocator
* */
condition?: (activeSection: string) => boolean;
/**
* The event handler
*/
handler: MapEventHandler;
};

/**
* Type of event called when no layer event was handled on a specific element,
* but there might be features under the event location. Ideal to handle
* multiple features, as `MapLayerEventHandlerDescriptor` will only handle the
* top-most selected element
*/
export type MapSelectEventHandlerDescriptor = {
/** Type for handlers that only require the layer to be active */
type: 'mapSelect';
layerName: string;
eventName: mapEventNames;
/**
* Condition function for which this handler applies. It will be checked
* before actually calling the handler, so the handler can assume this is
* true.
*
* TODO: This should depend on some application's state or context, that
* should be passed here. For now, we pass the active section and the rest
* can be accessed through the serviceLocator
* */
condition?: (activeSection: string) => boolean;
/**
* The event handler
*/
handler: MapSelectEventHandler;
};

export type MapLayerEventHandlerDescriptor = {
/** Type for handler that require selected features */
type: 'layer';
layerName: string;
eventName: layerEventNames;
/**
* Condition function for which this handler applies. It will be checked
* before actually calling the handler, so the handler can assume this is
* true.
*
* TODO: This should depend on some application's state or context, that
* should be passed here. For now, we pass the active section and the rest
* can be accessed through the serviceLocator
* */
condition?: (activeSection: string) => boolean;
/**
* The event handler
*/
handler: MapLayerEventHandler;
};

export type TooltipEventHandlerDescriptor = {
/** Type for handler that require selected features */
type: 'tooltip';
layerName: string;
eventName: tooltipEventNames;
/**
* Condition function for which this handler applies. It will be checked
* before actually calling the handler, so the handler can assume this is
* true.
*
* TODO: This should depend on some application's state or context, that
* should be passed here. For now, we pass the active section and the rest
* can be accessed through the serviceLocator
* */
condition?: (activeSection: string) => boolean;
/**
* The event handler
*/
handler: TooltipEventHandler;
};

export type MapEventHandlerDescription =
| {
/** Type for handler that require selected features */
type: 'layer';
layerName: string;
eventName: keyof MapboxGL.MapLayerEventType;
/**
* Condition function for which this handler applies. It will be checked
* before actually calling the handler, so the handler can assume this is
* true.
*
* TODO: This should depend on some application's state or context, that
* should be passed here. For now, we pass the active section and the rest
* can be accessed through the serviceLocator
* */
condition?: (activeSection: string) => boolean;
/**
* The event handler
*/
handler: MapLayerEventHandler;
}
| {
/** Type for handlers that only require the layer to be active */
type: 'map';
eventName: keyof MapboxGL.MapEventType;
/**
* Condition function for which this handler applies. It will be checked
* before actually calling the handler, so the handler can assume this is
* true.
*
* TODO: This should depend on some application's state or context, that
* should be passed here. For now, we pass the active section and the rest
* can be accessed through the serviceLocator
* */
condition?: (activeSection: string) => boolean;
/**
* The event handler
*/
handler: MapEventHandler;
};
| MapEventHandlerDescriptor
| MapLayerEventHandlerDescriptor
| TooltipEventHandlerDescriptor
| MapSelectEventHandlerDescriptor;
Loading
Loading