diff --git a/.gitignore b/.gitignore
index 92b2793..be9b12d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,5 @@
.direnv
+result
+*.duckdb
+notebooks
+static/data
diff --git a/duckdb/metro.sql b/duckdb/metro.sql
new file mode 100644
index 0000000..c2e10fa
--- /dev/null
+++ b/duckdb/metro.sql
@@ -0,0 +1,50 @@
+INSTALL spatial;
+LOAD spatial;
+
+CREATE OR REPLACE VIEW osm_quebec AS (
+ SELECT
+ *,
+ 'montreal' AS city
+ FROM
+ ST_READOSM (
+ '/nix/store/j56yncmjh7qgwa5k631vl70mdny7c5y0-data-dir/canada/quebec/montreal_quebec.osm.pbf'
+ )
+);
+
+CREATE OR REPLACE VIEW roads AS (
+ WITH
+ interresting_ways AS (SELECT * FROM osm_quebec
+ WHERE (
+ tags['highway'][1] = 'residential'
+ OR tags['highway'][1] = 'primary'
+ OR tags['highway'][1] = 'secondary'
+ OR tags['highway'][1] = 'tertiary'
+ )
+ AND city = 'montreal'),
+ relations AS (SELECT id,
+ tags,
+ UNNEST(refs) AS refs,
+ UNNEST(RANGE(LEN(refs))) as index
+ FROM interresting_ways),
+ road_points AS (SELECT osm_quebec.id AS id,
+ osm_quebec.lon AS lon,
+ osm_quebec.lat AS lat,
+ relations.tags AS tags,
+ relations.index AS relation_index,
+ relations.id AS relation_id
+ FROM relations
+ JOIN osm_quebec
+ ON osm_quebec.id = relations.refs
+ WHERE
+ osm_quebec.kind = 'node'
+ ORDER BY relation_id, relation_index)
+ SELECT
+ relation_id,
+ LIST([road_points.lat, road_points.lon]) AS points,
+ ANY_VALUE(tags) AS tags
+ FROM road_points
+ GROUP BY relation_id
+);
+
+COPY roads TO 'static/data/montreal.csv';
+COPY roads TO 'static/data/montreal.json';
diff --git a/flake.lock b/flake.lock
index ad7db7b..6d0e1c6 100644
--- a/flake.lock
+++ b/flake.lock
@@ -5,11 +5,11 @@
"systems": "systems"
},
"locked": {
- "lastModified": 1710146030,
- "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+ "lastModified": 1731533236,
+ "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
- "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+ "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -20,11 +20,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1724819573,
- "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=",
+ "lastModified": 1735291276,
+ "narHash": "sha256-NYVcA06+blsLG6wpAbSPTCyLvxD/92Hy4vlY9WxFI1M=",
"owner": "nixos",
"repo": "nixpkgs",
- "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2",
+ "rev": "634fd46801442d760e09493a794c4f15db2d0cbb",
"type": "github"
},
"original": {
@@ -34,22 +34,39 @@
"type": "github"
}
},
+ "quebec-cities-poly": {
+ "flake": false,
+ "locked": {
+ "lastModified": 1735484400,
+ "narHash": "sha256-poCHLXsdgX20cJiQVEamHkY6CpAJhbhMLJgtlFfQnKI=",
+ "owner": "didiercrunch",
+ "repo": "cities",
+ "rev": "c7b7612a5e8ba18831329238047a125ea5358db6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "didiercrunch",
+ "repo": "cities",
+ "type": "github"
+ }
+ },
"quebec-osm-data": {
"flake": false,
"locked": {
- "narHash": "sha256-wZnwOz49muoA1lXmXQ6LdHhVyt8vhG9Ex+6oWhts2FI=",
+ "narHash": "sha256-Mm7tzput0a4BiUeafCtVpuzPKnT13NfU6zU7o/bJZ3k=",
"type": "file",
- "url": "https://download.geofabrik.de/north-america/canada/quebec-240828.osm.pbf"
+ "url": "https://static.home.all-dressed-programming.com/download.geofabrik.de/north-america/canada/quebec-241223.osm.pbf"
},
"original": {
"type": "file",
- "url": "https://download.geofabrik.de/north-america/canada/quebec-240828.osm.pbf"
+ "url": "https://static.home.all-dressed-programming.com/download.geofabrik.de/north-america/canada/quebec-241223.osm.pbf"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
+ "quebec-cities-poly": "quebec-cities-poly",
"quebec-osm-data": "quebec-osm-data"
}
},
diff --git a/flake.nix b/flake.nix
index b0ec178..36f0eb1 100644
--- a/flake.nix
+++ b/flake.nix
@@ -4,21 +4,104 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
- quebec-osm-data.url =
- "https://download.geofabrik.de/north-america/canada/quebec-240828.osm.pbf";
- quebec-osm-data.flake = false;
+ quebec-osm-data = {
+ url =
+ "https://static.home.all-dressed-programming.com/download.geofabrik.de/north-america/canada/quebec-241223.osm.pbf";
+ flake = false;
+ };
+ quebec-cities-poly = {
+ url = "github:didiercrunch/cities";
+ flake = false;
+ };
};
-
- outputs = { self, nixpkgs, flake-utils, quebec-osm-data }:
+ outputs = { self, nixpkgs, flake-utils, quebec-osm-data, quebec-cities-poly }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
- default = pkgs.hello;
+
+ poly2geojson = pkgs.rustPlatform.buildRustPackage rec {
+ pname = "poly2geojson";
+ version = "0.1.3";
+
+ src = pkgs.fetchFromGitHub {
+ owner = "frafra";
+ repo = pname;
+ rev = "2fbf2383765fe043d43d53b91ddbfe50c0129bb1";
+ hash = "sha256-BMRl6fRqQlibuGxg5hBHz7r1cvionNYEZe9YcZuL0oU=";
+ };
+ cargoHash = "sha256-pCegHF6SsCIsCQ/b+N34xTMjw2PjPB0pi30Y9sWIcV4=";
+ };
+
+ osmosis = pkgs.stdenv.mkDerivation {
+ name = "osmosis";
+ src = pkgs.fetchzip {
+ url =
+ "https://github.com/openstreetmap/osmosis/releases/download/0.49.2/osmosis-0.49.2.zip";
+ hash = "sha256-hmD/pnEEJsl2NkODon4XZcZ3ZQ7+fx/JvWTR4xrRoug=";
+ };
+ buildInputs = [ ];
+ buildPhase = "";
+ installPhase = ''
+ cp -r . $out
+ '';
+ };
+
+ data-dir = pkgs.stdenv.mkDerivation {
+ name = "data-dir";
+ src = ./scripts;
+ buildInputs = [ poly2geojson ];
+ buildPhase = ''
+ mkdir -p cities/canada/quebec
+ ${pkgs.babashka}/bin/bb to-geojson.clj ${quebec-cities-poly}/canada/québec/* > cities.json
+ '';
+ installPhase = ''
+ mkdir -p $out/data
+ cp ${quebec-osm-data} $out/data/quebec.osm.pbf
+ cp cities.json $out/data
+ '';
+ };
+
+ dati = pkgs.stdenv.mkDerivation {
+ name = "data-dir";
+ src = ./scripts;
+ buildInputs = [ poly2geojson pkgs.jdk23 osmosis ];
+ buildPhase = ''
+ mkdir -p cities/canada/quebec
+ cp ${quebec-cities-poly}/canada/quebec/* cities/canada/quebec
+ chmod u+w cities/canada/quebec
+ ${pkgs.babashka}/bin/bb to-geojson.clj --osm-file ${quebec-osm-data} --paths cities/canada/quebec/*.poly
+ '';
+ installPhase = ''
+ cp -r cities $out
+ '';
+ };
+
+ python-virtual-env = pkgs.python312.withPackages (ps: [
+ ps.matplotlib
+ ps.jupyter
+ ps.duckdb
+ ps.pandas
+ ps.unidecode
+ ps.geopy
+ ]);
in {
- packages = { default = default; };
+ packages = {
+ default = data-dir;
+ dati = dati;
+ poly2geojson = poly2geojson;
+ osmosis = osmosis;
+ };
devShell = pkgs.mkShell {
name = "spacial-data-experiments";
- buildInputs = with pkgs; [ duckdb ];
+ buildInputs = with pkgs; [
+ duckdb
+ http-server
+ sqlfluff
+ poly2geojson
+ gdal
+ osmosis
+ python-virtual-env
+ ];
};
});
}
diff --git a/scripts/to-geojson.clj b/scripts/to-geojson.clj
new file mode 100644
index 0000000..d211de0
--- /dev/null
+++ b/scripts/to-geojson.clj
@@ -0,0 +1,41 @@
+(require '[babashka.process :refer [shell process exec]]
+ '[clojure.string :as strings]
+ '[babashka.cli :as cli])
+
+
+(defn extract-data [file]
+ (let [data (slurp file)
+ geojson (shell {:out :string :continue true :in data} "poly2geojson")
+ city-name (subs file
+ (inc (strings/last-index-of file "/"))
+ (strings/last-index-of file "_"))]
+ {:originalFile file
+ :geojson (some-> geojson
+ :out
+ (json/parse-string true)
+ :geometry
+ json/generate-string)
+ :province "québec"
+ :cityName city-name}))
+
+;; ${osmosis}/bin/osmosis --read-pbf-fast file="${quebec-osm-data}" \
+ ;; --bounding-polygon file="cities/canada/quebec/westmount_quebec.poly" \
+ ;; --write-pbf file="cities/canada/quebec/westmount_quebec.osm.pbf"
+
+(defn extract-osm-pbf [osm-file file]
+ (let [output-file (strings/replace file ".poly" ".osm.pbf")]
+ (println "Processing " file " file to " output-file)
+ (shell {:continue true}
+ "osmosis"
+ "--read-pbf-fast" (str "file=" osm-file)
+ "--bounding-polygon"
+ (str"file=" file)
+ "--write-pbf"
+ (str "file=" output-file))))
+
+
+(def opts (cli/parse-opts *command-line-args* {:coerce {:paths []}}))
+
+(doall (map
+ #(extract-osm-pbf (:osm-file opts) %)
+ (:paths opts)))
diff --git a/static/index-d3.js b/static/index-d3.js
new file mode 100644
index 0000000..13b3b65
--- /dev/null
+++ b/static/index-d3.js
@@ -0,0 +1,51 @@
+import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
+
+function convertCsvRow(row) {
+ return {
+ relation_id: +row.relation_id,
+ points: JSON.parse(row.points),
+ };
+}
+
+const roads = await d3.csv("./data/pincourt.csv", convertCsvRow);
+
+const projection = d3
+ .geoConicConformal()
+ .scale(100000)
+ .center([-73.983028645966, 45.37425995800737]);
+
+function drawRoad(context, road) {
+ if (!road || !road.points || road.points.length < 1) {
+ return context;
+ }
+ context.moveTo(...projection([road.points[0][0], road.points[0][1]]));
+ for (const datum of road.points) {
+ context.lineTo(...projection([datum[0], datum[1]]));
+ }
+ return context;
+}
+
+const svg = d3
+ .create("svg")
+ .attr("width", 975)
+ .attr("height", 610)
+ .attr("viewBox", [0, 0, 975, 610])
+ .attr("style", "max-width: 100%; height: auto;");
+
+svg
+ .append("g")
+ .attr("transform", "translate(610,20)")
+ .append("text")
+ .text("Some title");
+
+svg
+ .selectAll("path")
+ .data(roads)
+ .join("path")
+ .attr("fill", "none")
+ .attr("stroke", "black")
+ .attr("stroke-linejoin", "round")
+ .attr("stroke-with", 2)
+ .attr("d", (road) => drawRoad(d3.path(), road).toString());
+
+container.append(svg.node());
diff --git a/static/index-leaflet.js b/static/index-leaflet.js
new file mode 100644
index 0000000..c719932
--- /dev/null
+++ b/static/index-leaflet.js
@@ -0,0 +1,35 @@
+import leaflet from "https://esm.sh/leaflet@1.9.4";
+import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
+
+function convertCsvRow(row) {
+ return {
+ relation_id: +row.relation_id,
+ points: JSON.parse(row.points),
+ };
+}
+
+function getCenter(data) {
+ if (!data) {
+ return [51.505, -0.09]; // london
+ }
+ return data[0].points[0];
+}
+
+function addRoad(road, map) {
+ leaflet.polyline(road.points).addTo(map);
+}
+
+function addRoads(roads, map) {
+ for (const road of roads) {
+ addRoad(road, map);
+ }
+}
+
+const roads = await d3.csv("./data/montreal.csv", convertCsvRow);
+
+const map = leaflet.map("leaflet-map").setView(getCenter(roads), 13);
+
+leaflet
+ .tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { maxZoom: 19 })
+ .addTo(map);
+addRoads(roads, map);
diff --git a/static/index.html b/static/index.html
index 514fccb..bd8c4c1 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,13 +1,23 @@
-
-
-
+
+
-
+
spacial-data-experiments
+
+
-
-
-
+
+
+
+
+
+
+
diff --git a/static/index.js b/static/index.js
deleted file mode 100644
index a2621a2..0000000
--- a/static/index.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
-
-import * as topojson from "https://cdn.jsdelivr.net/npm/topojson@3/+esm";
-
-const roadUrl = './road.json';
-const road = await (await fetch(roadUrl)).json()
-
-const projection = d3.geoConicConformal()
- .scale(12000)
- .center([-72.949, 46.879])
-
-
-
-function drawRoads(context, data) {
- if(!data || data.length < 1){
- return context;
- }
- context.moveTo(...projection([data[0].lon, data[0].lat]));
- for(const datum of data){
- context.lineTo(...projection([datum.lon, datum.lat]));
- }
- return context;
-}
-
-const svg = d3.create("svg")
- .attr("width", 975)
- .attr("height", 610)
- .attr("viewBox", [0, 0, 975, 610])
- .attr("style", "max-width: 100%; height: auto;");
-
-svg.append("g")
- .attr("transform", "translate(610,20)")
- .append('text').text("fuck me");
-
-svg.append("path")
- .data([road])
- .attr("fill", "none")
- .attr("stroke", "black")
- .attr("stroke-linejoin", "round")
- .attr("stroke-with", 2)
- .attr("d", d => drawRoads(d3.path(), d).toString());
-
-container.append(svg.node());
diff --git a/static/road.json b/static/road.json
deleted file mode 100644
index efc5bd1..0000000
--- a/static/road.json
+++ /dev/null
@@ -1,62 +0,0 @@
-[{"kind":"node","id":16874054,"tags":null,"refs":null,"lat":45.5292308,"lon":-74.55641920000001,"ref_roles":null,"ref_types":null,"refs_1":16874054},
-{"kind":"node","id":1452900835,"tags":null,"refs":null,"lat":45.5311146,"lon":-74.5546107,"ref_roles":null,"ref_types":null,"refs_1":1452900835},
-{"kind":"node","id":16874055,"tags":null,"refs":null,"lat":45.532989,"lon":-74.55260270000001,"ref_roles":null,"ref_types":null,"refs_1":16874055},
-{"kind":"node","id":1452900864,"tags":null,"refs":null,"lat":45.542531000000004,"lon":-74.5416177,"ref_roles":null,"ref_types":null,"refs_1":1452900864},
-{"kind":"node","id":4512670916,"tags":null,"refs":null,"lat":45.5433042,"lon":-74.54055190000001,"ref_roles":null,"ref_types":null,"refs_1":4512670916},
-{"kind":"node","id":16874058,"tags":null,"refs":null,"lat":45.544044400000004,"lon":-74.5394361,"ref_roles":null,"ref_types":null,"refs_1":16874058},
-{"kind":"node","id":4512670917,"tags":null,"refs":null,"lat":45.5446192,"lon":-74.53849190000001,"ref_roles":null,"ref_types":null,"refs_1":4512670917},
-{"kind":"node","id":1452900866,"tags":null,"refs":null,"lat":45.545194,"lon":-74.5374298,"ref_roles":null,"ref_types":null,"refs_1":1452900866},
-{"kind":"node","id":16874059,"tags":null,"refs":null,"lat":45.5461273,"lon":-74.53556760000001,"ref_roles":null,"ref_types":null,"refs_1":16874059},
-{"kind":"node","id":4512670918,"tags":null,"refs":null,"lat":45.546693000000005,"lon":-74.5342433,"ref_roles":null,"ref_types":null,"refs_1":4512670918},
-{"kind":"node","id":1452900868,"tags":null,"refs":null,"lat":45.547448100000004,"lon":-74.53218340000001,"ref_roles":null,"ref_types":null,"refs_1":1452900868},
-{"kind":"node","id":17053331,"tags":null,"refs":null,"lat":45.548865600000006,"lon":-74.5284827,"ref_roles":null,"ref_types":null,"refs_1":17053331},
-{"kind":"node","id":1452900882,"tags":null,"refs":null,"lat":45.5497377,"lon":-74.52613190000001,"ref_roles":null,"ref_types":null,"refs_1":1452900882},
-{"kind":"node","id":17053333,"tags":null,"refs":null,"lat":45.5506867,"lon":-74.5239093,"ref_roles":null,"ref_types":null,"refs_1":17053333},
-{"kind":"node","id":3506421397,"tags":null,"refs":null,"lat":45.551139500000005,"lon":-74.5229566,"ref_roles":null,"ref_types":null,"refs_1":3506421397},
-{"kind":"node","id":1452900886,"tags":null,"refs":null,"lat":45.551902600000005,"lon":-74.5216305,"ref_roles":null,"ref_types":null,"refs_1":1452900886},
-{"kind":"node","id":3506421404,"tags":null,"refs":null,"lat":45.5525632,"lon":-74.5206493,"ref_roles":null,"ref_types":null,"refs_1":3506421404},
-{"kind":"node","id":3506421403,"tags":null,"refs":null,"lat":45.5540165,"lon":-74.5188466,"ref_roles":null,"ref_types":null,"refs_1":3506421403},
-{"kind":"node","id":1452900890,"tags":null,"refs":null,"lat":45.5549678,"lon":-74.5178784,"ref_roles":null,"ref_types":null,"refs_1":1452900890},
-{"kind":"node","id":5748462429,"tags":null,"refs":null,"lat":45.5605861,"lon":-74.51205780000001,"ref_roles":null,"ref_types":null,"refs_1":5748462429},
-{"kind":"node","id":1452900894,"tags":null,"refs":null,"lat":45.562221,"lon":-74.5103639,"ref_roles":null,"ref_types":null,"refs_1":1452900894},
-{"kind":"node","id":3506421401,"tags":null,"refs":null,"lat":45.5630547,"lon":-74.50927030000001,"ref_roles":null,"ref_types":null,"refs_1":3506421401},
-{"kind":"node","id":1699110086,"tags":null,"refs":null,"lat":45.563304200000005,"lon":-74.5088868,"ref_roles":null,"ref_types":null,"refs_1":1699110086},
-{"kind":"node","id":3506421396,"tags":null,"refs":null,"lat":45.5636285,"lon":-74.50834230000001,"ref_roles":null,"ref_types":null,"refs_1":3506421396},
-{"kind":"node","id":1699110096,"tags":null,"refs":null,"lat":45.5640571,"lon":-74.5074692,"ref_roles":null,"ref_types":null,"refs_1":1699110096},
-{"kind":"node","id":3506421424,"tags":null,"refs":null,"lat":45.5642687,"lon":-74.5069479,"ref_roles":null,"ref_types":null,"refs_1":3506421424},
-{"kind":"node","id":1699110099,"tags":null,"refs":null,"lat":45.564606100000006,"lon":-74.50595750000001,"ref_roles":null,"ref_types":null,"refs_1":1699110099},
-{"kind":"node","id":294347577,"tags":null,"refs":null,"lat":45.564881400000004,"lon":-74.50497130000001,"ref_roles":null,"ref_types":null,"refs_1":294347577},
-{"kind":"node","id":17053336,"tags":null,"refs":null,"lat":45.565148300000004,"lon":-74.5033525,"ref_roles":null,"ref_types":null,"refs_1":17053336},
-{"kind":"node","id":3506421438,"tags":null,"refs":null,"lat":45.565200700000005,"lon":-74.50279450000001,"ref_roles":null,"ref_types":null,"refs_1":3506421438},
-{"kind":"node","id":17053337,"tags":null,"refs":null,"lat":45.5652404,"lon":-74.50223820000001,"ref_roles":null,"ref_types":null,"refs_1":17053337},
-{"kind":"node","id":3506421433,"tags":null,"refs":null,"lat":45.5652534,"lon":-74.5017481,"ref_roles":null,"ref_types":null,"refs_1":3506421433},
-{"kind":"node","id":3506421449,"tags":null,"refs":null,"lat":45.5652552,"lon":-74.5013563,"ref_roles":null,"ref_types":null,"refs_1":3506421449},
-{"kind":"node","id":1452900897,"tags":null,"refs":null,"lat":45.5652357,"lon":-74.5007033,"ref_roles":null,"ref_types":null,"refs_1":1452900897},
-{"kind":"node","id":294346765,"tags":null,"refs":null,"lat":45.565069300000005,"lon":-74.4991076,"ref_roles":null,"ref_types":null,"refs_1":294346765},
-{"kind":"node","id":16874063,"tags":null,"refs":null,"lat":45.5633618,"lon":-74.4917568,"ref_roles":null,"ref_types":null,"refs_1":16874063},
-{"kind":"node","id":16874064,"tags":null,"refs":null,"lat":45.5603345,"lon":-74.4790139,"ref_roles":null,"ref_types":null,"refs_1":16874064},
-{"kind":"node","id":17053339,"tags":null,"refs":null,"lat":45.5579422,"lon":-74.46892840000001,"ref_roles":null,"ref_types":null,"refs_1":17053339},
-{"kind":"node","id":503820619,"tags":{"highway":"motorway_junction","ref":"5"},"refs":null,"lat":45.5551072,"lon":-74.4576126,"ref_roles":null,"ref_types":null,"refs_1":503820619},
-{"kind":"node","id":16874071,"tags":null,"refs":null,"lat":45.5532872,"lon":-74.4503572,"ref_roles":null,"ref_types":null,"refs_1":16874071},
-{"kind":"node","id":16874073,"tags":null,"refs":null,"lat":45.552988500000005,"lon":-74.4491962,"ref_roles":null,"ref_types":null,"refs_1":16874073},
-{"kind":"node","id":16874074,"tags":null,"refs":null,"lat":45.5521059,"lon":-74.44576260000001,"ref_roles":null,"ref_types":null,"refs_1":16874074},
-{"kind":"node","id":16874075,"tags":null,"refs":null,"lat":45.550453700000006,"lon":-74.43921800000001,"ref_roles":null,"ref_types":null,"refs_1":16874075},
-{"kind":"node","id":16874076,"tags":null,"refs":null,"lat":45.549582900000004,"lon":-74.4354764,"ref_roles":null,"ref_types":null,"refs_1":16874076},
-{"kind":"node","id":4512670915,"tags":null,"refs":null,"lat":45.5494045,"lon":-74.4344518,"ref_roles":null,"ref_types":null,"refs_1":4512670915},
-{"kind":"node","id":4512670914,"tags":null,"refs":null,"lat":45.549094600000004,"lon":-74.43196,"ref_roles":null,"ref_types":null,"refs_1":4512670914},
-{"kind":"node","id":4512670913,"tags":null,"refs":null,"lat":45.5489424,"lon":-74.430694,"ref_roles":null,"ref_types":null,"refs_1":4512670913},
-{"kind":"node","id":16874079,"tags":null,"refs":null,"lat":45.548533000000006,"lon":-74.42820760000001,"ref_roles":null,"ref_types":null,"refs_1":16874079},
-{"kind":"node","id":4512670912,"tags":null,"refs":null,"lat":45.5480652,"lon":-74.4262442,"ref_roles":null,"ref_types":null,"refs_1":4512670912},
-{"kind":"node","id":16874080,"tags":null,"refs":null,"lat":45.5468161,"lon":-74.4212794,"ref_roles":null,"ref_types":null,"refs_1":16874080},
-{"kind":"node","id":4603837886,"tags":null,"refs":null,"lat":45.545283500000004,"lon":-74.41547650000001,"ref_roles":null,"ref_types":null,"refs_1":4603837886},
-{"kind":"node","id":2028173902,"tags":null,"refs":null,"lat":45.5281766,"lon":-74.5572753,"ref_roles":null,"ref_types":null,"refs_1":2028173902},
-{"kind":"node","id":16874057,"tags":null,"refs":null,"lat":45.540989800000006,"lon":-74.5434379,"ref_roles":null,"ref_types":null,"refs_1":16874057},
-{"kind":"node","id":17053334,"tags":null,"refs":null,"lat":45.5533184,"lon":-74.51963570000001,"ref_roles":null,"ref_types":null,"refs_1":17053334},
-{"kind":"node","id":1699110085,"tags":null,"refs":null,"lat":45.5625209,"lon":-74.5100056,"ref_roles":null,"ref_types":null,"refs_1":1699110085},
-{"kind":"node","id":3506421423,"tags":null,"refs":null,"lat":45.5639343,"lon":-74.50772350000001,"ref_roles":null,"ref_types":null,"refs_1":3506421423},
-{"kind":"node","id":3506421410,"tags":null,"refs":null,"lat":45.565178200000005,"lon":-74.4999356,"ref_roles":null,"ref_types":null,"refs_1":3506421410},
-{"kind":"node","id":3506421445,"tags":null,"refs":null,"lat":45.5648618,"lon":-74.49801140000001,"ref_roles":null,"ref_types":null,"refs_1":3506421445},
-{"kind":"node","id":16873636,"tags":null,"refs":null,"lat":45.5643016,"lon":-74.4956882,"ref_roles":null,"ref_types":null,"refs_1":16873636},
-{"kind":"node","id":3506421443,"tags":null,"refs":null,"lat":45.5607385,"lon":-74.4807037,"ref_roles":null,"ref_types":null,"refs_1":3506421443},
-{"kind":"node","id":16874078,"tags":null,"refs":null,"lat":45.548848500000005,"lon":-74.4299001,"ref_roles":null,"ref_types":null,"refs_1":16874078}
-]