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} -]