]> Wikimedia Canada | Git repositories - eccc_map.git/blob - eccc_map.js
Initial version
[eccc_map.git] / eccc_map.js
1 /*
2 * eccc_map - Tool to show a map of ECCC stations and generate a list based on
3 * a polygon drawn by user.
4 * Copyright (C) 2020 Pierre Choffet
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as
8 * published by the Free Software Foundation, either version 3 of the
9 * License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20
21 // Download station inventory as distributed with eccc_map since we cannot
22 // get the upstream one that is on a ftp server.
23 // Original URL: ftp://client_climate@ftp.tor.ec.gc.ca/Pub/Get_More_Data_Plus_de_donnees/Station Inventory EN.csv
24 const csv_url = 'Station Inventory EN.csv';
25 const map = L.map('map').setView([70.44,-88.07], 4);
26 const draws = new L.FeatureGroup();
27 const cluster = L.markerClusterGroup();
28 const icon = L.icon({iconUrl: 'leaflet/marker-icon.png', shadowUrl: 'leaflet/marker-shadow.png'});
29 var markers_selected = [];
30
31 L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
32 id: 'osm',
33 attribution: '<a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors',
34 maxZoom: 18,
35 tileSize: 256
36 }
37 ).addTo(map);
38
39 // Activate draw
40 map.addLayer(draws);
41 const draw_control = new L.Control.Draw({
42 edit: {
43 featureGroup: draws
44 },
45 draw: {
46 polygon: {
47 showArea: true
48 },
49 polyline: false,
50 rectangle: false,
51 circle: false,
52 marker: false,
53 circlemarker: false
54 },
55 edit: false
56 });
57
58 map.on(L.Draw.Event.DRAWSTART, function (event) {
59 draws.clearLayers();
60 });
61
62 map.on(L.Draw.Event.CREATED, function (event) {
63 const layer = event.layer;
64 const polygon_latlngs = layer.getLatLngs();
65
66 draws.addLayer(layer);
67
68 markers_selected = [];
69
70 cluster.eachLayer(function(e) {
71 if (e instanceof L.Marker) {
72 let inside = false;
73 const marker_lat = e.getLatLng().lat;
74 const marker_lon = e.getLatLng().lng;
75
76 for (let latlngs_browse = 0; latlngs_browse < polygon_latlngs.length; ++latlngs_browse){
77 let points = polygon_latlngs[latlngs_browse];
78
79 for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
80 let xi = points[i].lat;
81 let yi = points[i].lng;
82 let xj = points[j].lat;
83 let yj = points[j].lng;
84
85 let intersect = ((yi > marker_lon) != (yj > marker_lon)) && (marker_lat < (xj - xi) * (marker_lon - yi) / (yj - yi) + xi);
86 if (intersect)
87 inside = !inside;
88 }
89 }
90
91 if (inside) {
92 markers_selected.push(e);
93 }
94
95 updateOutput();
96 }
97 });
98 });
99
100 map.addControl(draw_control);
101
102 // Add custom draw button click event
103 let button = document.getElementById('draw-polygon');
104 button.addEventListener('click', function() {draw_control._toolbars.draw._modes.polygon.handler.enable();});
105
106 // Fetch csv file, read it line by line
107 async function readStationsEntries(callback) {
108 let text = '';
109
110 fetch(csv_url).then(
111 function(response) {
112 return response.body.getReader();
113 }
114 ).then(
115 function (reader) {
116 reader.read().then(
117 function readChunk({done, value}) {
118 if (done)
119 return;
120
121 const decoder = new TextDecoder();
122 text += decoder.decode(value);
123
124 return reader.read().then(readChunk);
125 }
126 ).then(
127 function () {
128 const text_split = text.split("\n");
129 const lines_count = text_split.length;
130 let lines_browse = 0;
131
132 // Consume csv header
133 for (; lines_browse < lines_count; ++lines_browse) {
134 if (text_split[lines_browse].startsWith('"Name",'))
135 break;
136 }
137 ++lines_browse;
138
139 // Read stations rows
140 for (; lines_browse < lines_count - 1; ++lines_browse) {
141 // To keep code simple, we expect CSV entries to be surrounded by
142 // two '"' characters and containing no additional one in their values.
143 // We just count occurences of '"' per line to ensure sanity.
144 if (text_split[lines_browse].split('"').length != 39) {
145 console.error('Illegal station entry: line '+(lines_browse+1));
146 continue;
147 }
148
149 const entry = text_split[lines_browse].split(',');
150 const name = entry[0].replaceAll('"', '');
151 const cid = entry[2].replaceAll('"', '');
152 const lat = entry[6].replaceAll('"', '');
153 const lon = entry[7].replaceAll('"', '');
154
155 const marker = L.marker([lat, lon], {icon: icon, cid: cid});
156 marker.bindPopup('<h1>'+name+'</h1><h2>'+cid+'</h2>');
157 marker.addTo(cluster);
158 }
159
160 map.addLayer(cluster);
161 }
162 )
163 }
164 )
165 }
166
167 function updateOutput() {
168 let output = '';
169 for (let markers_browse = 0; markers_browse < markers_selected.length; ++markers_browse) {
170 if (markers_browse != 0)
171 output += document.getElementById('separator').value;
172
173 output += document.getElementById('pattern').value.replace('{cid}', markers_selected[markers_browse].options.cid);
174 }
175 document.getElementById('output').value = output;
176 }
177
178 readStationsEntries();
179
180 // Add events in settings fields
181 document.getElementById('pattern').addEventListener('blur', updateOutput);
182 document.getElementById('separator').addEventListener('blur', updateOutput);