@local-logic/maps 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @local-logic/maps
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - 892a059: LL22-5368: Updated storybook github action
8
+ - Updated dependencies [892a059]
9
+ - @local-logic/design-system@0.7.12
10
+
11
+ ## 0.0.3
12
+
13
+ ### Patch Changes
14
+
15
+ - de9946a: LL22-5133: Added layer support to map package
16
+
3
17
  ## 0.0.2
4
18
 
5
19
  ### Patch Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@local-logic/maps",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "This is a maps implementation allowing for the display of Local Logic data on a map.",
5
5
  "author": "Local Logic",
6
6
  "license": "ISC",
@@ -14,14 +14,14 @@
14
14
  "scripts": {
15
15
  "dev": "vite",
16
16
  "build": "vite build",
17
+ "build:storybook": "storybook build",
17
18
  "test": "vitest run",
18
19
  "stats": "STATS=1 vite build",
19
20
  "size": "yarn run build && size-limit",
20
21
  "lint": "TIMING=1 eslint --ext .js,.jsx,.ts,.tsx .",
21
22
  "check-types": "tsc --project ./tsconfig.json --noEmit",
22
23
  "clean": "rm -rf .turbo && rm -rf node_modules && rm -rf dist",
23
- "storybook:run": "storybook dev -p 6006",
24
- "storybook:build": "storybook build"
24
+ "storybook": "storybook dev -p 6006"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "react": ">=18",
@@ -0,0 +1,97 @@
1
+ import React, { useState, useEffect, useContext } from "react";
2
+
3
+ // Work inspired from:
4
+ // https://github.com/visgl/react-google-maps/blob/main/examples/geometry/src/components/polygon.tsx
5
+ // https://visgl.github.io/react-google-maps/examples/geometry
6
+
7
+ import { GoogleMapsContext } from "@vis.gl/react-google-maps";
8
+
9
+ import { getGooglePolygonFromLocalLogicGeometry } from "../utils";
10
+
11
+ import type { Props, Layer as LayerType } from "../types";
12
+
13
+ function getLayerPaint(type: LayerType["type"], color: string) {
14
+ switch (type) {
15
+ case "fill":
16
+ return {
17
+ fillColor: color,
18
+ fillOpacity: 0.25,
19
+ };
20
+ case "line":
21
+ return {
22
+ strokeColor: color,
23
+ strokeWeight: 2,
24
+ };
25
+ default:
26
+ return {};
27
+ }
28
+ }
29
+
30
+ export default function GoogleLayers({ sources }: Props) {
31
+ const [map, setMap] = useState<google.maps.Map | null>(null);
32
+ const [polygonRefs, setPolygonRefs] = useState<google.maps.Polygon[]>([]);
33
+ const mapContext = useContext(GoogleMapsContext);
34
+
35
+ useEffect(() => {
36
+ if (!mapContext) {
37
+ return;
38
+ }
39
+
40
+ setMap(mapContext.map);
41
+ }, [mapContext]);
42
+
43
+ useEffect(() => {
44
+ // Remove existing polygons
45
+ polygonRefs.forEach((polygonRef) => {
46
+ polygonRef?.setMap(null);
47
+ });
48
+
49
+ if (!map || typeof sources === "undefined" || sources.length === 0) {
50
+ return;
51
+ }
52
+
53
+ // Create new polygons
54
+ const newPolygons = sources.map((source) => {
55
+ const polygonOptions = {
56
+ paths: getGooglePolygonFromLocalLogicGeometry(source.geometry),
57
+ };
58
+
59
+ if (Array.isArray(source.layer)) {
60
+ source.layer.forEach((layer) => {
61
+ const layerPaint = getLayerPaint(layer.type, layer.color);
62
+ Object.assign(polygonOptions, layerPaint);
63
+ });
64
+ } else {
65
+ const layerPaint = getLayerPaint(source.layer.type, source.layer.color);
66
+ Object.assign(polygonOptions, layerPaint);
67
+ }
68
+
69
+ const newPolygon = new google.maps.Polygon({
70
+ // Set default options otherwise the polygon remains visible (even if eg: we only want the outline)
71
+ // We then overrider the default options with the layer options
72
+ strokeWeight: 0,
73
+ fillOpacity: 0,
74
+ ...polygonOptions,
75
+ });
76
+ newPolygon.setMap(map);
77
+
78
+ return newPolygon;
79
+ });
80
+
81
+ setPolygonRefs(newPolygons);
82
+ // Set the map for each polygon
83
+ newPolygons.forEach((polygonRef) => {
84
+ polygonRef.setMap(map);
85
+ });
86
+
87
+ // Clean up the polygons when the component unmounts or when sources change
88
+ // eslint-disable-next-line consistent-return
89
+ return () => {
90
+ newPolygons.forEach((polygonRef) => {
91
+ polygonRef?.setMap(null);
92
+ });
93
+ };
94
+ }, [map, sources]);
95
+
96
+ return <></>;
97
+ }
@@ -0,0 +1,74 @@
1
+ import React, { useMemo } from "react";
2
+
3
+ import { Source, Layer } from "react-map-gl/mapbox";
4
+
5
+ import { getFeatureCollectionFromLocalLogicGeometry } from "../utils";
6
+
7
+ import type { Props, Layer as LayerType } from "../types";
8
+
9
+ function getLayerPaint(type: LayerType["type"], color: string) {
10
+ switch (type) {
11
+ case "fill":
12
+ return {
13
+ "fill-color": color,
14
+ "fill-opacity": 0.15,
15
+ };
16
+ case "line":
17
+ return {
18
+ "line-color": color,
19
+ "line-width": 2,
20
+ };
21
+ default:
22
+ return {};
23
+ }
24
+ }
25
+
26
+ export default function MapboxLayers({ sources }: Props) {
27
+ const geometrySources = useMemo(() => {
28
+ if (!Array.isArray(sources) || sources.length === 0) {
29
+ return [];
30
+ }
31
+
32
+ return sources.map((geometry) => ({
33
+ ...geometry,
34
+ geoJson: geometry.geometry
35
+ ? getFeatureCollectionFromLocalLogicGeometry(geometry.geometry)
36
+ : null,
37
+ }));
38
+ }, [sources]);
39
+
40
+ if (geometrySources?.length === 0) {
41
+ return null;
42
+ }
43
+
44
+ return (
45
+ <>
46
+ {geometrySources?.map((source) => (
47
+ <div key={source.key}>
48
+ {source.geoJson && (
49
+ <Source id={source.key} key={source.key} type="geojson" data={source.geoJson}>
50
+ {source.layer &&
51
+ (Array.isArray(source.layer) ? (
52
+ source.layer.map((layer, index) => (
53
+ <Layer
54
+ key={`${source.key}-${index}`}
55
+ {...{
56
+ ...{ type: layer.type, paint: getLayerPaint(layer.type, layer.color) },
57
+ }}
58
+ />
59
+ ))
60
+ ) : (
61
+ <Layer
62
+ {...{
63
+ type: source.layer.type,
64
+ paint: getLayerPaint(source.layer.type, source.layer.color),
65
+ }}
66
+ />
67
+ ))}
68
+ </Source>
69
+ )}
70
+ </div>
71
+ ))}
72
+ </>
73
+ );
74
+ }
@@ -0,0 +1,74 @@
1
+ import React, { useMemo } from "react";
2
+
3
+ import { Source, Layer } from "react-map-gl/maplibre";
4
+
5
+ import { getFeatureCollectionFromLocalLogicGeometry } from "../utils";
6
+
7
+ import type { Props, Layer as LayerType } from "../types";
8
+
9
+ function getLayerPaint(type: LayerType["type"], color: string) {
10
+ switch (type) {
11
+ case "fill":
12
+ return {
13
+ "fill-color": color,
14
+ "fill-opacity": 0.25,
15
+ };
16
+ case "line":
17
+ return {
18
+ "line-color": color,
19
+ "line-width": 2,
20
+ };
21
+ default:
22
+ return {};
23
+ }
24
+ }
25
+
26
+ export default function MaptilerLayers({ sources }: Props) {
27
+ const geometrySources = useMemo(() => {
28
+ if (!Array.isArray(sources) || sources.length === 0) {
29
+ return [];
30
+ }
31
+
32
+ return sources.map((geometry) => ({
33
+ ...geometry,
34
+ geoJson: geometry.geometry
35
+ ? getFeatureCollectionFromLocalLogicGeometry(geometry.geometry)
36
+ : null,
37
+ }));
38
+ }, [sources]);
39
+
40
+ if (geometrySources?.length === 0) {
41
+ return null;
42
+ }
43
+
44
+ return (
45
+ <>
46
+ {geometrySources?.map((source) => (
47
+ <div key={source.key}>
48
+ {source.geoJson && (
49
+ <Source id={source.key} key={source.key} type="geojson" data={source.geoJson}>
50
+ {source.layer &&
51
+ (Array.isArray(source.layer) ? (
52
+ source.layer.map((layer, index) => (
53
+ <Layer
54
+ key={`${source.key}-${index}`}
55
+ {...{
56
+ ...{ type: layer.type, paint: getLayerPaint(layer.type, layer.color) },
57
+ }}
58
+ />
59
+ ))
60
+ ) : (
61
+ <Layer
62
+ {...{
63
+ type: source.layer.type,
64
+ paint: getLayerPaint(source.layer.type, source.layer.color),
65
+ }}
66
+ />
67
+ ))}
68
+ </Source>
69
+ )}
70
+ </div>
71
+ ))}
72
+ </>
73
+ );
74
+ }
@@ -0,0 +1,32 @@
1
+ import React, { lazy, useMemo } from "react";
2
+
3
+ import type { Props } from "./types";
4
+
5
+ import { useRootElement } from "../context";
6
+
7
+ const MaptilerLayers = lazy(() => import("./Maptiler"));
8
+ const GoogleLayers = lazy(() => import("./Google"));
9
+ const MapboxLayers = lazy(() => import("./Mapbox"));
10
+
11
+ export function Layers(props: Props) {
12
+ const { mapProvider } = useRootElement();
13
+
14
+ const BaseLayers = useMemo(() => {
15
+ switch (mapProvider?.name) {
16
+ case "maptiler":
17
+ return MaptilerLayers;
18
+ case "google":
19
+ return GoogleLayers;
20
+ case "mapbox":
21
+ return MapboxLayers;
22
+ default:
23
+ return null;
24
+ }
25
+ }, [mapProvider]);
26
+
27
+ if (!BaseLayers) {
28
+ return null;
29
+ }
30
+
31
+ return <BaseLayers {...props} />;
32
+ }
@@ -0,0 +1,15 @@
1
+ export type Layer = {
2
+ type: "fill" | "line";
3
+ color: string;
4
+ };
5
+
6
+ export type Source = {
7
+ key: string;
8
+ layer: Layer | Layer[];
9
+ // Expected [lat, lng] format
10
+ geometry: number[][][][];
11
+ };
12
+
13
+ export type Props = {
14
+ sources?: Source[];
15
+ };
@@ -0,0 +1,64 @@
1
+ import type { FeatureCollection, MultiPolygon } from "geojson";
2
+
3
+ type Geometry =
4
+ | number[][]
5
+ | number[][][]
6
+ | number[][][][]
7
+ | string[][]
8
+ | string[][][]
9
+ | string[][][][];
10
+
11
+ export function getFeatureCollectionFromLocalLogicGeometry(geometry: Geometry = []) {
12
+ /* Geometries need formatting before being added as a Source in the Map.
13
+ * Geometries from v3/schools have a format of [lat, lng]
14
+ * Geojson accepts polygons with a format of [lng, lat]
15
+ */
16
+
17
+ const geometryLatLngFormat = (arr: object): object =>
18
+ Object.values(arr).map((a) => {
19
+ if (Array.isArray(a[0])) {
20
+ return geometryLatLngFormat(a);
21
+ }
22
+
23
+ return [a[1], a[0]];
24
+ });
25
+
26
+ const finalCoordinates = geometryLatLngFormat(geometry);
27
+
28
+ const responseObject = {
29
+ type: "FeatureCollection",
30
+ features: [
31
+ {
32
+ type: "Feature",
33
+ geometry: {
34
+ type: "MultiPolygon",
35
+ coordinates: finalCoordinates,
36
+ },
37
+ },
38
+ ],
39
+ } as FeatureCollection<MultiPolygon>;
40
+
41
+ return responseObject;
42
+ }
43
+
44
+ export function getGooglePolygonFromLocalLogicGeometry(geometry: Geometry = []) {
45
+ /* Geometries need formatting before being added as a Source in the Map.
46
+ * Geometries from v3/schools have a format of [lat, lng]
47
+ * Google Maps accepts polygons with a format of { lat: number, lng: number }
48
+ */
49
+
50
+ const geometryLatLngFormat = (arr: object): object =>
51
+ Object.values(arr).map((a) => {
52
+ if (Array.isArray(a[0])) {
53
+ return geometryLatLngFormat(a.flat());
54
+ }
55
+
56
+ return { lat: a[0], lng: a[1] };
57
+ });
58
+
59
+ const finalCoordinates = geometryLatLngFormat(
60
+ geometry
61
+ ) as google.maps.MVCArray<google.maps.LatLng>;
62
+
63
+ return finalCoordinates;
64
+ }
@@ -1,9 +1,18 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { PointFeature, ClusterProperties } from "supercluster";
3
- import type { Marker } from "../types";
3
+ import type { Icon } from "@phosphor-icons/react";
4
+ import type { Coordinates } from "../types";
4
5
 
5
6
  export type ClusterPoint = PointFeature<ClusterProperties & Marker>;
6
7
 
8
+ export type Marker = {
9
+ id: string;
10
+ latitude: Coordinates["latitude"];
11
+ longitude: Coordinates["longitude"];
12
+ name?: string;
13
+ icon?: Icon;
14
+ };
15
+
7
16
  export type MarkerPoint = {
8
17
  id: string;
9
18
  latitude: number;
@@ -12,7 +12,19 @@ export function Root(props: RootProps) {
12
12
  );
13
13
  }
14
14
 
15
+ // BASE MAP
15
16
  export { BaseMap } from "./BaseMap";
17
+
18
+ // MARKERS
16
19
  export { Markers } from "./Markers";
20
+ export type { MarkerProps, Marker, MarkerPoint } from "./Markers/types";
21
+
22
+ // POPUP
17
23
  export { Popup } from "./Popup";
18
- export type { MarkerProps, MarkerPoint } from "./Markers/types";
24
+
25
+ // LAYERS
26
+ export { Layers } from "./Layers";
27
+ export type { Source, Layer } from "./Layers/types";
28
+
29
+ // OTHER
30
+ export type { RootProps };
@@ -1,5 +1,4 @@
1
1
  import type { ReactNode } from "react";
2
- import { Icon } from "@phosphor-icons/react";
3
2
 
4
3
  /**
5
4
  * We should try to keep the types "agnostic" but if we have to pick a side,
@@ -17,14 +16,6 @@ export type Coordinates = {
17
16
  longitude: number;
18
17
  };
19
18
 
20
- export type Marker = {
21
- id: string;
22
- latitude: Coordinates["latitude"];
23
- longitude: Coordinates["longitude"];
24
- name?: string;
25
- icon?: Icon;
26
- };
27
-
28
19
  export type ZoomPosition = "top-left" | "top-right" | "bottom-left" | "bottom-right";
29
20
 
30
21
  export type RootProps = {
@@ -35,6 +26,5 @@ export type RootProps = {
35
26
  pitch?: number;
36
27
  bearing?: number;
37
28
  cooperativeGestures?: boolean;
38
- markers?: Marker[];
39
29
  children?: ReactNode;
40
30
  };
@@ -1,25 +1,21 @@
1
1
  import React, { useState, useMemo } from "react";
2
2
  import { StoryFn, Meta } from "@storybook/react";
3
3
  import { mapDefaults as storybookMapDefaults } from "~/../.storybook/defaults";
4
- import {
5
- Storefront,
6
- ShoppingCartSimple,
7
- Brandy,
8
- ForkKnife,
9
- Coffee,
10
- Baby,
11
- Train,
12
- Barbell,
13
- GasPump,
14
- Heartbeat,
15
- } from "@phosphor-icons/react";
16
- import type { RootProps, Marker } from "./Root/types";
17
4
  import { defaultMapValues } from "./Root/constants";
18
- import * as Map from "./Root";
5
+ import {
6
+ Root as MapRoot,
7
+ BaseMap,
8
+ Markers,
9
+ Popup,
10
+ Layers,
11
+ type RootProps,
12
+ type MarkerPoint,
13
+ } from "./Root";
14
+ import { themes, markerList, layerSources } from "./storybook-data";
19
15
 
20
16
  export default {
21
17
  title: "Map",
22
- component: Map.Root,
18
+ component: MapRoot,
23
19
  argTypes: storybookMapDefaults,
24
20
  } as Meta<RootProps>;
25
21
 
@@ -32,88 +28,10 @@ const defaultValues = {
32
28
  zoomPosition: "bottom-right" as RootProps["zoomPosition"],
33
29
  };
34
30
 
35
- const markerList = [
36
- {
37
- id: "marker-1",
38
- name: "Marker 1",
39
- latitude: 45.527399,
40
- longitude: -73.598126,
41
- icon: Storefront,
42
- },
43
- {
44
- id: "marker-2",
45
- name: "Marker 2",
46
- latitude: 45.527302,
47
- longitude: -73.597405,
48
- icon: ShoppingCartSimple,
49
- },
50
- {
51
- id: "marker-3",
52
- name: "Marker 3",
53
- latitude: 45.527302,
54
- longitude: -73.597405,
55
- icon: Brandy,
56
- },
57
- {
58
- id: "marker-4",
59
- name: "Marker 4",
60
- latitude: 45.527302,
61
- longitude: -73.597405,
62
- icon: ForkKnife,
63
- },
64
- {
65
- id: "marker-5",
66
- name: "Marker 5",
67
- latitude: 45.527302,
68
- longitude: -73.597405,
69
- icon: Coffee,
70
- },
71
- {
72
- id: "marker-6",
73
- name: "Marker 6",
74
- latitude: 45.527302,
75
- longitude: -73.597405,
76
- icon: Baby,
77
- },
78
- {
79
- id: "marker-7",
80
- name: "Marker 7",
81
- latitude: 45.527256,
82
- longitude: -73.600229,
83
- icon: Train,
84
- },
85
- {
86
- id: "marker-8",
87
- name: "Marker 8",
88
- latitude: 45.527256,
89
- longitude: -73.600229,
90
- icon: Barbell,
91
- },
92
- {
93
- id: "marker-9",
94
- name: "Marker 9",
95
- latitude: 45.526643,
96
- longitude: -73.600293,
97
- icon: GasPump,
98
- },
99
- {
100
- id: "marker-10",
101
- name: "Marker 10",
102
- latitude: 45.527025,
103
- longitude: -73.600897,
104
- icon: Heartbeat,
105
- },
106
- ] as Marker[];
107
-
108
- const maptilerThemes = {
109
- day: "600d69cb-288d-445e-9839-3dfe4d76b31a",
110
- night: "dd191599-2a92-49fc-9a33-e12391753ad5",
111
- };
112
-
113
31
  const Template: StoryFn<RootProps> = (args) => {
114
- const [activeMarker, setActiveMarker] = useState<Map.MarkerPoint | undefined>(undefined);
32
+ const [activeMarker, setActiveMarker] = useState<MarkerPoint | undefined>(undefined);
115
33
 
116
- const handleMarkerClick = (markers: Map.MarkerPoint) => {
34
+ const handleMarkerClick = (markers: MarkerPoint) => {
117
35
  if (
118
36
  activeMarker?.latitude === markers.latitude &&
119
37
  activeMarker?.longitude === markers.longitude
@@ -168,19 +86,20 @@ const Template: StoryFn<RootProps> = (args) => {
168
86
 
169
87
  return (
170
88
  <div className="w-full h-[calc(100vh-30px)]">
171
- <Map.Root {...args}>
172
- <Map.BaseMap>
173
- <Map.Markers markers={markerList} onClick={handleMarkerClick} />
89
+ <MapRoot {...args}>
90
+ <BaseMap>
91
+ <Layers sources={layerSources} />
92
+ <Markers markers={markerList} onClick={handleMarkerClick} />
174
93
 
175
- <Map.Popup
94
+ <Popup
176
95
  latitude={activeMarker?.latitude}
177
96
  longitude={activeMarker?.longitude}
178
97
  onClose={() => setActiveMarker(undefined)}
179
98
  >
180
99
  {popupContent}
181
- </Map.Popup>
182
- </Map.BaseMap>
183
- </Map.Root>
100
+ </Popup>
101
+ </BaseMap>
102
+ </MapRoot>
184
103
  </div>
185
104
  );
186
105
  };
@@ -190,7 +109,7 @@ Maptiler.args = {
190
109
  mapProvider: {
191
110
  name: "maptiler",
192
111
  apiKey: import.meta.env.VITE_MAPTILER_KEY,
193
- maptilerTheme: maptilerThemes.day,
112
+ maptilerTheme: themes.maptiler.day,
194
113
  },
195
114
  ...defaultValues,
196
115
  };
@@ -0,0 +1,255 @@
1
+ import {
2
+ Storefront,
3
+ ShoppingCartSimple,
4
+ Brandy,
5
+ ForkKnife,
6
+ Coffee,
7
+ Baby,
8
+ Train,
9
+ Barbell,
10
+ GasPump,
11
+ Heartbeat,
12
+ } from "@phosphor-icons/react";
13
+
14
+ import type { Marker, Source } from "./Root";
15
+
16
+ export const themes = {
17
+ maptiler: {
18
+ day: "600d69cb-288d-445e-9839-3dfe4d76b31a",
19
+ night: "dd191599-2a92-49fc-9a33-e12391753ad5",
20
+ },
21
+ mapbox: {
22
+ day: "",
23
+ night: "",
24
+ },
25
+ google: {
26
+ day: "",
27
+ night: "",
28
+ },
29
+ };
30
+
31
+ export const markerList = [
32
+ {
33
+ id: "marker-1",
34
+ name: "Marker 1",
35
+ latitude: 45.527399,
36
+ longitude: -73.598126,
37
+ icon: Storefront,
38
+ },
39
+ {
40
+ id: "marker-2",
41
+ name: "Marker 2",
42
+ latitude: 45.527302,
43
+ longitude: -73.597405,
44
+ icon: ShoppingCartSimple,
45
+ },
46
+ {
47
+ id: "marker-3",
48
+ name: "Marker 3",
49
+ latitude: 45.527302,
50
+ longitude: -73.597405,
51
+ icon: Brandy,
52
+ },
53
+ {
54
+ id: "marker-4",
55
+ name: "Marker 4",
56
+ latitude: 45.527302,
57
+ longitude: -73.597405,
58
+ icon: ForkKnife,
59
+ },
60
+ {
61
+ id: "marker-5",
62
+ name: "Marker 5",
63
+ latitude: 45.527302,
64
+ longitude: -73.597405,
65
+ icon: Coffee,
66
+ },
67
+ {
68
+ id: "marker-6",
69
+ name: "Marker 6",
70
+ latitude: 45.527302,
71
+ longitude: -73.597405,
72
+ icon: Baby,
73
+ },
74
+ {
75
+ id: "marker-7",
76
+ name: "Marker 7",
77
+ latitude: 45.527256,
78
+ longitude: -73.600229,
79
+ icon: Train,
80
+ },
81
+ {
82
+ id: "marker-8",
83
+ name: "Marker 8",
84
+ latitude: 45.527256,
85
+ longitude: -73.600229,
86
+ icon: Barbell,
87
+ },
88
+ {
89
+ id: "marker-9",
90
+ name: "Marker 9",
91
+ latitude: 45.526643,
92
+ longitude: -73.600293,
93
+ icon: GasPump,
94
+ },
95
+ {
96
+ id: "marker-10",
97
+ name: "Marker 10",
98
+ latitude: 45.527025,
99
+ longitude: -73.600897,
100
+ icon: Heartbeat,
101
+ },
102
+ ] as Marker[];
103
+
104
+ export const layerSources: Source[] = [
105
+ {
106
+ key: "neighborhood",
107
+ layer: [
108
+ {
109
+ type: "fill",
110
+ color: "#008491",
111
+ },
112
+ {
113
+ type: "line",
114
+ color: "#008491",
115
+ },
116
+ ],
117
+ geometry: [
118
+ [
119
+ [
120
+ [45.53006, -73.59634],
121
+ [45.52986, -73.59589],
122
+ [45.52982, -73.59582],
123
+ [45.52894, -73.59389],
124
+ [45.52864, -73.59321],
125
+ [45.52799, -73.59174],
126
+ [45.52626, -73.58795],
127
+ [45.52478, -73.58473],
128
+ [45.52396, -73.58292],
129
+ [45.52337, -73.58342],
130
+ [45.52332, -73.58347],
131
+ [45.52289, -73.58385],
132
+ [45.52242, -73.58426],
133
+ [45.52216, -73.58451],
134
+ [45.52164, -73.58499],
135
+ [45.5201, -73.58638],
136
+ [45.51835, -73.58796],
137
+ [45.51765, -73.58854],
138
+ [45.51753, -73.58864],
139
+ [45.51711, -73.58901],
140
+ [45.51695, -73.58915],
141
+ [45.51677, -73.58932],
142
+ [45.51679, -73.58938],
143
+ [45.51649, -73.58965],
144
+ [45.51639, -73.58973],
145
+ [45.51633, -73.58978],
146
+ [45.5163, -73.58981],
147
+ [45.51602, -73.59007],
148
+ [45.5158, -73.59027],
149
+ [45.51585, -73.59038],
150
+ [45.5159, -73.59049],
151
+ [45.51625, -73.59125],
152
+ [45.5163, -73.59136],
153
+ [45.51632, -73.5914],
154
+ [45.51652, -73.59185],
155
+ [45.51667, -73.5922],
156
+ [45.51682, -73.59253],
157
+ [45.51695, -73.59282],
158
+ [45.51704, -73.59302],
159
+ [45.51712, -73.5932],
160
+ [45.51721, -73.59339],
161
+ [45.51738, -73.59379],
162
+ [45.51742, -73.59387],
163
+ [45.51757, -73.59422],
164
+ [45.51797, -73.59513],
165
+ [45.5181, -73.59542],
166
+ [45.51861, -73.59655],
167
+ [45.51902, -73.59748],
168
+ [45.51917, -73.59781],
169
+ [45.51939, -73.59832],
170
+ [45.51944, -73.59843],
171
+ [45.51948, -73.59853],
172
+ [45.51963, -73.59887],
173
+ [45.52018, -73.6001],
174
+ [45.52076, -73.60143],
175
+ [45.52127, -73.60258],
176
+ [45.52138, -73.60283],
177
+ [45.52147, -73.60303],
178
+ [45.52206, -73.60435],
179
+ [45.52213, -73.60452],
180
+ [45.52228, -73.60485],
181
+ [45.52241, -73.60515],
182
+ [45.52245, -73.60523],
183
+ [45.52249, -73.60531],
184
+ [45.52252, -73.6054],
185
+ [45.52256, -73.60548],
186
+ [45.5226, -73.60556],
187
+ [45.52278, -73.60597],
188
+ [45.52285, -73.60614],
189
+ [45.52294, -73.60634],
190
+ [45.52309, -73.60667],
191
+ [45.52324, -73.607],
192
+ [45.52335, -73.60725],
193
+ [45.52349, -73.60758],
194
+ [45.52366, -73.60796],
195
+ [45.52376, -73.60819],
196
+ [45.52387, -73.60843],
197
+ [45.52463, -73.61015],
198
+ [45.52468, -73.61026],
199
+ [45.52468, -73.61026],
200
+ [45.525, -73.61098],
201
+ [45.52537, -73.61179],
202
+ [45.5256, -73.61233],
203
+ [45.52565, -73.61242],
204
+ [45.52566, -73.6124],
205
+ [45.5263, -73.61124],
206
+ [45.52644, -73.611],
207
+ [45.5277, -73.60871],
208
+ [45.52783, -73.60839],
209
+ [45.52804, -73.60786],
210
+ [45.52817, -73.60739],
211
+ [45.52822, -73.6072],
212
+ [45.52823, -73.60716],
213
+ [45.52825, -73.60704],
214
+ [45.52828, -73.60689],
215
+ [45.52829, -73.60685],
216
+ [45.52831, -73.60672],
217
+ [45.52831, -73.6067],
218
+ [45.52837, -73.60626],
219
+ [45.52838, -73.60619],
220
+ [45.52838, -73.60614],
221
+ [45.52839, -73.60576],
222
+ [45.52837, -73.60459],
223
+ [45.52837, -73.60457],
224
+ [45.52839, -73.6035],
225
+ [45.52842, -73.60238],
226
+ [45.52849, -73.60167],
227
+ [45.52849, -73.60167],
228
+ [45.5285, -73.60159],
229
+ [45.5285, -73.60159],
230
+ [45.52854, -73.60122],
231
+ [45.52857, -73.60103],
232
+ [45.52871, -73.60014],
233
+ [45.52873, -73.6],
234
+ [45.52873, -73.6],
235
+ [45.52878, -73.59978],
236
+ [45.52878, -73.59977],
237
+ [45.5289, -73.59929],
238
+ [45.52905, -73.5988],
239
+ [45.5291, -73.59861],
240
+ [45.52916, -73.59844],
241
+ [45.52916, -73.59844],
242
+ [45.5293, -73.59807],
243
+ [45.52931, -73.59803],
244
+ [45.52934, -73.59796],
245
+ [45.52956, -73.5974],
246
+ [45.52966, -73.59715],
247
+ [45.52985, -73.59678],
248
+ [45.53004, -73.5964],
249
+ [45.53007, -73.59635],
250
+ [45.53006, -73.59634],
251
+ ],
252
+ ],
253
+ ],
254
+ },
255
+ ];