From e8f29e07d7591e5877c590a579c7e2c5b38f2e1e Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 19 Aug 2024 13:12:18 +0200 Subject: [PATCH 1/4] [Task] #109, start polyline --- httpdocs/images/marker.svg | 4 ++++ src/client/components/Map.tsx | 5 ++++- src/client/css/map.css | 9 +++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 httpdocs/images/marker.svg create mode 100644 src/client/css/map.css diff --git a/httpdocs/images/marker.svg b/httpdocs/images/marker.svg new file mode 100644 index 00000000..e8200326 --- /dev/null +++ b/httpdocs/images/marker.svg @@ -0,0 +1,4 @@ + + Marker Arrow + + \ No newline at end of file diff --git a/src/client/components/Map.tsx b/src/client/components/Map.tsx index c6e8eaf9..2800d844 100644 --- a/src/client/components/Map.tsx +++ b/src/client/components/Map.tsx @@ -1,10 +1,11 @@ import React, { useEffect } from 'react' -import { MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet' +import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from 'react-leaflet' import 'leaflet/dist/leaflet.css'; import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'; // Re-uses images from ~leaflet package // import L from 'leaflet'; import 'leaflet-defaulticon-compatibility'; import "../css/map.css"; +import { LatLngExpression } from "leaflet"; // Used to recenter the map to new coordinates @@ -27,6 +28,7 @@ function Map({ entries }: { entries: Models.IEntry[] }) { const lastEntry = entries.at(-1); const cleanEntries = entries.filter((entry) => !entry.ignore); + const polyline: LatLngExpression[] = cleanEntries.map((entry) => ([entry.lat, entry.lon])); return ( @@ -46,6 +48,7 @@ function Map({ entries }: { entries: Models.IEntry[] }) { ) })} + ) diff --git a/src/client/css/map.css b/src/client/css/map.css new file mode 100644 index 00000000..48ff09b6 --- /dev/null +++ b/src/client/css/map.css @@ -0,0 +1,9 @@ +.mapContainer { + height: 100%; +} + + +.leaflet-popup-content { + font-size: 1.2rem; + min-width: min-content; +} \ No newline at end of file From f8f37ca5fdd28040d1e40f4e4e20c543b4c31d7a Mon Sep 17 00:00:00 2001 From: Type-Style Date: Mon, 19 Aug 2024 20:09:32 +0200 Subject: [PATCH 2/4] [Task] #94, marker --- httpdocs/css/base.css | 5 ++++ httpdocs/images/marker.svg | 2 +- package-lock.json | 19 +++++++++++++-- package.json | 2 ++ src/client/components/Map.tsx | 46 +++++++++++++++++++++++++++++++---- src/client/css/map.css | 24 ++++++++++++++++++ 6 files changed, 90 insertions(+), 8 deletions(-) diff --git a/httpdocs/css/base.css b/httpdocs/css/base.css index fd4fe7f1..20418533 100644 --- a/httpdocs/css/base.css +++ b/httpdocs/css/base.css @@ -130,6 +130,8 @@ Neutral: #131211 --text: color-mix(in oklch, var(--neutral) 20%, black); --textOnColor: var(--neutral); --semiBg: #ffffffbb; + --contrastText: white; + --contrastBackground: black; --baseFontWeightModifier: 50; @@ -148,6 +150,9 @@ Neutral: #131211 --text: color-mix(in oklch, var(--neutral) 20%, white); --semiBg: #00000077; + --contrastText: black; + --contrastBackground: white; + --baseFontWeightModifier: -50; } } diff --git a/httpdocs/images/marker.svg b/httpdocs/images/marker.svg index e8200326..da4c914c 100644 --- a/httpdocs/images/marker.svg +++ b/httpdocs/images/marker.svg @@ -1,4 +1,4 @@ Marker Arrow - + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 17de58ef..a6b3203b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.15.20", "@mui/material": "^5.15.20", + "@types/leaflet-rotatedmarker": "^0.2.5", "axios": "^1.7.4", "bcrypt": "^5.1.1", "chalk": "^4.1.2", @@ -27,6 +28,7 @@ "jsonwebtoken": "^9.0.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", + "leaflet-rotatedmarker": "^0.2.0", "module-alias": "^2.2.3", "react": "^18.3.1", "react-dom": "^18.3.1", @@ -2107,7 +2109,6 @@ "version": "7946.0.14", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.14.tgz", "integrity": "sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==", - "dev": true, "license": "MIT" }, "node_modules/@types/graceful-fs": { @@ -2193,12 +2194,20 @@ "version": "1.9.12", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", - "dev": true, "license": "MIT", "dependencies": { "@types/geojson": "*" } }, + "node_modules/@types/leaflet-rotatedmarker": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@types/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.5.tgz", + "integrity": "sha512-GaKK1bdQ6NYGkVdZj2cHe8Eu1BVf40Jhtmd8pZj5gQSJcTy5iTog0hsMIhf6QQDKnaEgrRJzm4OES6B9vxi4dw==", + "license": "MIT", + "dependencies": { + "@types/leaflet": "*" + } + }, "node_modules/@types/mime": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", @@ -7456,6 +7465,12 @@ "integrity": "sha512-IrKagWxkTwzxUkFIumy/Zmo3ksjuAu3zEadtOuJcKzuXaD76Gwvg2Z1mLyx7y52ykOzM8rAH5ChBs4DnfdGa6Q==", "license": "BSD-2-Clause" }, + "node_modules/leaflet-rotatedmarker": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz", + "integrity": "sha512-yc97gxLXwbZa+Gk9VCcqI0CkvIBC9oNTTjFsHqq4EQvANrvaboib4UdeQLyTnEqDpaXHCqzwwVIDHtvz2mUiDg==", + "license": "MIT" + }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", diff --git a/package.json b/package.json index 0ce8da03..28f2835d 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@emotion/styled": "^11.11.5", "@mui/icons-material": "^5.15.20", "@mui/material": "^5.15.20", + "@types/leaflet-rotatedmarker": "^0.2.5", "axios": "^1.7.4", "bcrypt": "^5.1.1", "chalk": "^4.1.2", @@ -81,6 +82,7 @@ "jsonwebtoken": "^9.0.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", + "leaflet-rotatedmarker": "^0.2.0", "module-alias": "^2.2.3", "react": "^18.3.1", "react-dom": "^18.3.1", diff --git a/src/client/components/Map.tsx b/src/client/components/Map.tsx index 2800d844..9b1c8967 100644 --- a/src/client/components/Map.tsx +++ b/src/client/components/Map.tsx @@ -1,13 +1,13 @@ import React, { useEffect } from 'react' import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from 'react-leaflet' +import L, { Icon, IconOptions } from 'leaflet'; +import 'leaflet-rotatedmarker'; import 'leaflet/dist/leaflet.css'; -import 'leaflet-defaulticon-compatibility/dist/leaflet-defaulticon-compatibility.webpack.css'; // Re-uses images from ~leaflet package -// import L from 'leaflet'; -import 'leaflet-defaulticon-compatibility'; import "../css/map.css"; import { LatLngExpression } from "leaflet"; + // Used to recenter the map to new coordinates const MapRecenter = ({ lat, lon, zoom }: { lat: number, lon: number, zoom: number }) => { const map = useMap(); @@ -30,6 +30,37 @@ function Map({ entries }: { entries: Models.IEntry[] }) { const polyline: LatLngExpression[] = cleanEntries.map((entry) => ([entry.lat, entry.lon])); + + // Function to create custom icon with dynamic className + function createCustomIcon(entry: Models.IEntry) { + let className = ""; + let iconSize = 15; + if (entry.index == 0) { + className = "start" + } + if (entry == lastEntry) { + className = "end" + } + + if (className) { + iconSize = 22; + } + + return L.divIcon({ + html: ` + + Marker Arrow + + `, + shadowUrl: null, + shadowSize: null, + shadowAnchor: null, + iconSize: [iconSize, iconSize], + iconAnchor: [iconSize/2, iconSize/2], + popupAnchor: [0, 0], + className: `customMarkerIcon ${className}`, + }); + } return ( @@ -38,9 +69,14 @@ function Map({ entries }: { entries: Models.IEntry[] }) { url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" /> {cleanEntries.map((entry) => { - console.log(entry.index); return ( - +
{JSON.stringify(entry, null, 2)}
diff --git a/src/client/css/map.css b/src/client/css/map.css index 48ff09b6..97989ebc 100644 --- a/src/client/css/map.css +++ b/src/client/css/map.css @@ -6,4 +6,28 @@ .leaflet-popup-content { font-size: 1.2rem; min-width: min-content; +} + + +.customMarkerIcon { + + &.start, &.end { + display: flex; + place-content: center; + border: 2px solid var(--contrastBackground); + outline: 3px solid var(--contrastBackground); + outline-offset: 3px; + border-radius: 50%; + background: var(--contrastBackground); + + svg { + height: 80%; + } + } + + &.start { + outline: none; + } + + } \ No newline at end of file From 65a8adc0def529f5893b2ef220e56d5d65bbdd95 Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 21 Aug 2024 16:16:57 +0200 Subject: [PATCH 3/4] [Task] #109, gradient color polyline color based on speed --- package-lock.json | 28 +++++++++ package.json | 3 + src/client/components/Map.tsx | 100 ++++++++++++++++++++++++++------- src/client/css/map.css | 4 ++ src/client/tsconfig.json | 2 +- src/client/types_polyline.d.ts | 14 +++++ 6 files changed, 129 insertions(+), 22 deletions(-) create mode 100644 src/client/types_polyline.d.ts diff --git a/package-lock.json b/package-lock.json index a6b3203b..4e9c94ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "bcrypt": "^5.1.1", "chalk": "^4.1.2", "compression": "^1.7.4", + "culori": "^4.0.1", "dotenv": "^16.4.5", "ejs": "^3.1.10", "express": "^4.19.2", @@ -28,6 +29,7 @@ "jsonwebtoken": "^9.0.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", + "leaflet-polycolor": "^2.0.5", "leaflet-rotatedmarker": "^0.2.0", "module-alias": "^2.2.3", "react": "^18.3.1", @@ -41,6 +43,7 @@ "@tsconfig/node20": "^20.1.4", "@types/bcrypt": "^5.0.2", "@types/compression": "^1.7.5", + "@types/culori": "^2.1.1", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", @@ -2055,6 +2058,13 @@ "@types/node": "*" } }, + "node_modules/@types/culori": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/culori/-/culori-2.1.1.tgz", + "integrity": "sha512-NzLYD0vNHLxTdPp8+RlvGbR2NfOZkwxcYGFwxNtm+WH2NuUNV8785zv1h0sulFQ5aFQ9n/jNDUuJeo3Bh7+oFA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "8.56.7", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.7.tgz", @@ -4044,6 +4054,15 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/culori": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/culori/-/culori-4.0.1.tgz", + "integrity": "sha512-LSnjA6HuIUOlkfKVbzi2OlToZE8OjFi667JWN9qNymXVXzGDmvuP60SSgC+e92sd7B7158f7Fy3Mb6rXS5EDPw==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -7465,6 +7484,15 @@ "integrity": "sha512-IrKagWxkTwzxUkFIumy/Zmo3ksjuAu3zEadtOuJcKzuXaD76Gwvg2Z1mLyx7y52ykOzM8rAH5ChBs4DnfdGa6Q==", "license": "BSD-2-Clause" }, + "node_modules/leaflet-polycolor": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/leaflet-polycolor/-/leaflet-polycolor-2.0.5.tgz", + "integrity": "sha512-nksE5PlCgzULil8rDzGfOnVH1o62GKyT4oLFyaqXUEidwcCMDvKr7x4DTCDdpUjiaoOLYBZTKwCT2XA0bfgExQ==", + "license": "MIT", + "dependencies": { + "leaflet": "^1.9.2" + } + }, "node_modules/leaflet-rotatedmarker": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/leaflet-rotatedmarker/-/leaflet-rotatedmarker-0.2.0.tgz", diff --git a/package.json b/package.json index 28f2835d..9dfec8e4 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@tsconfig/node20": "^20.1.4", "@types/bcrypt": "^5.0.2", "@types/compression": "^1.7.5", + "@types/culori": "^2.1.1", "@types/express": "^4.17.21", "@types/hpp": "^0.2.5", "@types/jest": "^29.5.11", @@ -71,6 +72,7 @@ "bcrypt": "^5.1.1", "chalk": "^4.1.2", "compression": "^1.7.4", + "culori": "^4.0.1", "dotenv": "^16.4.5", "ejs": "^3.1.10", "express": "^4.19.2", @@ -82,6 +84,7 @@ "jsonwebtoken": "^9.0.2", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2", + "leaflet-polycolor": "^2.0.5", "leaflet-rotatedmarker": "^0.2.0", "module-alias": "^2.2.3", "react": "^18.3.1", diff --git a/src/client/components/Map.tsx b/src/client/components/Map.tsx index 9b1c8967..ec4b5fe4 100644 --- a/src/client/components/Map.tsx +++ b/src/client/components/Map.tsx @@ -1,23 +1,76 @@ import React, { useEffect } from 'react' -import { MapContainer, Marker, Polyline, Popup, TileLayer, useMap } from 'react-leaflet' -import L, { Icon, IconOptions } from 'leaflet'; +import { MapContainer, Marker, Popup, TileLayer, useMap } from 'react-leaflet' +import leafletPolycolor from 'leaflet-polycolor'; +import { formatRgb, toGamut, parse, Oklch } from 'culori'; +import L, { LatLngExpression } from 'leaflet'; import 'leaflet-rotatedmarker'; import 'leaflet/dist/leaflet.css'; import "../css/map.css"; -import { LatLngExpression } from "leaflet"; - +leafletPolycolor(L); // Used to recenter the map to new coordinates const MapRecenter = ({ lat, lon, zoom }: { lat: number, lon: number, zoom: number }) => { const map = useMap(); - useEffect(() => { // Fly to that coordinates and set new zoom level map.flyTo([lat, lon], zoom); }, [lat, lon]); return null; +}; + +const MultiColorPolyline = ({ cleanEntries }: { cleanEntries: Models.IEntry[] }) => { + const map = useMap(); + const useRelativeColors = true; // Change candidate; Use color in range to maximum speed, like from 0 to max, rather than fixed range + + function calculateHue(baseHue, maxSpeed, currentSpeed) { + // range of currentSpeed and maxSpeed transfered to range from 0 to 360 + let hueOffset = (currentSpeed / maxSpeed) * 360; + // add baseHue to the hueOffset and overflow at 360 + let hue = (baseHue + hueOffset) % 360; + + return hue; +} + + useEffect(() => { + if (map) { + + let maxSpeed = 0; + if (useRelativeColors) { + maxSpeed = cleanEntries.reduce((maxSpeed, entry) => { + // compare the current entry's GPS speed with the maxSpeed found so far + return Math.max(maxSpeed, entry.speed.gps); + }, cleanEntries[0].speed.gps); + } + maxSpeed *= 3.6; // convert M/S to KM/h + + const colorsArray = cleanEntries.map((entry) => { + const startColor = parse('oklch(62.8% 0.2577 29.23)') as Oklch; // red + const currentSpeed = entry.speed.gps * 3.6; // convert to km/h + + + startColor.h = calculateHue(startColor.h, maxSpeed, currentSpeed); + startColor.l = currentSpeed > maxSpeed * 0.8 ? startColor.l = currentSpeed / maxSpeed : startColor.l; + + const rgbInGamut = toGamut('rgb', 'oklch', null)(startColor); // map OKLCH to the RGB gamut + const colorRgb = formatRgb(rgbInGamut); // format the result as an RGB string + + + + return colorRgb; + }); + + const polylineArray: LatLngExpression[] = cleanEntries.map((entry) => ([entry.lat, entry.lon])); + + const polyline = L.polycolor(polylineArray, { + colors: colorsArray, + weight: 5 + }).addTo(map); + } + }, [map]); + + return null; }; function Map({ entries }: { entries: Models.IEntry[] }) { @@ -28,8 +81,6 @@ function Map({ entries }: { entries: Models.IEntry[] }) { const lastEntry = entries.at(-1); const cleanEntries = entries.filter((entry) => !entry.ignore); - const polyline: LatLngExpression[] = cleanEntries.map((entry) => ([entry.lat, entry.lon])); - // Function to create custom icon with dynamic className function createCustomIcon(entry: Models.IEntry) { @@ -56,13 +107,14 @@ function Map({ entries }: { entries: Models.IEntry[] }) { shadowSize: null, shadowAnchor: null, iconSize: [iconSize, iconSize], - iconAnchor: [iconSize/2, iconSize/2], + iconAnchor: [iconSize / 2, iconSize / 2], popupAnchor: [0, 0], className: `customMarkerIcon ${className}`, }); } + return ( - + {cleanEntries.map((entry) => { return ( - - -
{JSON.stringify(entry, null, 2)}
-
-
+
+ + +
{JSON.stringify(entry, null, 2)}
+
+
+ + +
) })} - + + +
) diff --git a/src/client/css/map.css b/src/client/css/map.css index 97989ebc..8bf913d0 100644 --- a/src/client/css/map.css +++ b/src/client/css/map.css @@ -8,6 +8,10 @@ min-width: min-content; } +.leaflet-overlay-pane canvas { /* polyline */ + filter: drop-shadow(0px 0px 3px var(--neutral)); +} + .customMarkerIcon { diff --git a/src/client/tsconfig.json b/src/client/tsconfig.json index 7777c586..953d1179 100644 --- a/src/client/tsconfig.json +++ b/src/client/tsconfig.json @@ -6,5 +6,5 @@ "jsx": "react", "esModuleInterop": true }, - "include": ["**/*.tsx", "**/*.ts", "types.d.ts", "../../types.d.ts"] + "include": ["**/*.tsx", "**/*.ts", "types.d.ts", "../../types.d.ts", "types_polyline.d.ts"] } \ No newline at end of file diff --git a/src/client/types_polyline.d.ts b/src/client/types_polyline.d.ts new file mode 100644 index 00000000..f955d35d --- /dev/null +++ b/src/client/types_polyline.d.ts @@ -0,0 +1,14 @@ +// Polyline plugin for native Leaflet +import * as L from 'leaflet'; + +declare module 'leaflet' { + namespace Polycolor { + interface Options { + colors: Array; + weight?: number; + } + } + + // Declare the actual polycolor function under the L namespace + function polycolor(latlngs: L.LatLngExpression[], options: Polycolor.Options): L.Polyline; +} From 390e882173b5b4cfedd6605159ca26155e9cf1bc Mon Sep 17 00:00:00 2001 From: Type-Style Date: Wed, 21 Aug 2024 21:40:55 +0200 Subject: [PATCH 4/4] [Task] #109 linter fixes --- src/client/components/Map.tsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/client/components/Map.tsx b/src/client/components/Map.tsx index ec4b5fe4..bc8aeb3a 100644 --- a/src/client/components/Map.tsx +++ b/src/client/components/Map.tsx @@ -24,46 +24,42 @@ const MultiColorPolyline = ({ cleanEntries }: { cleanEntries: Models.IEntry[] }) const useRelativeColors = true; // Change candidate; Use color in range to maximum speed, like from 0 to max, rather than fixed range function calculateHue(baseHue, maxSpeed, currentSpeed) { - // range of currentSpeed and maxSpeed transfered to range from 0 to 360 - let hueOffset = (currentSpeed / maxSpeed) * 360; - // add baseHue to the hueOffset and overflow at 360 - let hue = (baseHue + hueOffset) % 360; + // range of currentSpeed and maxSpeed transfered to range from 0 to 360 + const hueOffset = (currentSpeed / maxSpeed) * 360; + // add baseHue to the hueOffset and overflow at 360 + const hue = (baseHue + hueOffset) % 360; - return hue; -} + return hue; + } useEffect(() => { if (map) { - let maxSpeed = 0; + if (useRelativeColors) { maxSpeed = cleanEntries.reduce((maxSpeed, entry) => { // compare the current entry's GPS speed with the maxSpeed found so far return Math.max(maxSpeed, entry.speed.gps); }, cleanEntries[0].speed.gps); + maxSpeed *= 3.6; // convert M/S to KM/h } - maxSpeed *= 3.6; // convert M/S to KM/h const colorsArray = cleanEntries.map((entry) => { const startColor = parse('oklch(62.8% 0.2577 29.23)') as Oklch; // red const currentSpeed = entry.speed.gps * 3.6; // convert to km/h - - + startColor.h = calculateHue(startColor.h, maxSpeed, currentSpeed); startColor.l = currentSpeed > maxSpeed * 0.8 ? startColor.l = currentSpeed / maxSpeed : startColor.l; const rgbInGamut = toGamut('rgb', 'oklch', null)(startColor); // map OKLCH to the RGB gamut const colorRgb = formatRgb(rgbInGamut); // format the result as an RGB string - - - return colorRgb; }); const polylineArray: LatLngExpression[] = cleanEntries.map((entry) => ([entry.lat, entry.lon])); - const polyline = L.polycolor(polylineArray, { + L.polycolor(polylineArray, { colors: colorsArray, weight: 5 }).addTo(map);