1// Set map defaults 2var map = new mapboxgl.Map({ 3 container: "map", 4 style: 'mapbox://styles/northamerican/ckf7suzpd0gx219mhjwhbqb0s', 5 center: [-73.566520, 45.500100], // Gare Centrale 6 7 bearing: -57, // -57 degrees is north 8 zoom: 12, 9 attributionControl: false 10 }); 11 12// Create legend 13var legendContainer = document.createElement("div"); 14var legendTitle = document.createElement("h4"); 15var legend50plus = document.createElement("div"); 16var legend10plus = document.createElement("div"); 17var legend3plus = document.createElement("div"); 18var legendLess3 = document.createElement("div"); 19var legendUndetermined = document.createElement("div"); 20 21// Set content 22legendContainer.id = "legend"; 23legendTitle.innerHTML = i18next.t("LEGEND_TITLE"); 24legend50plus.innerHTML = `<span style="background-color: ${color4}"></span>50+ ${i18next.t("PROPERTIES")}`; 25legend10plus.innerHTML = `<span style="background-color: ${color3}"></span>10+ ${i18next.t("PROPERTIES")}`; 26legend3plus.innerHTML = `<span style="background-color: ${color2}"></span>3+ ${i18next.t("PROPERTIES")}`; 27legendLess3.innerHTML = `<label><span style="background-color: ${color1}"></span>1-2 ${i18next.t("PROPERTIES")} 28 <input id="showPropertiesLess3" type="checkbox">${i18next.t("VISIBLE")}</label>`; 29legendUndetermined.innerHTML = `<span style="background-color: ${white}"></span># ${i18next.t("NOT_DETERMINED")}`; 30 31// Add attribution control 32var attributionControl = new mapboxgl.AttributionControl({ 33 customAttribution: `<a href="mailto:${supportEmail}"><b>${i18next.t("IMPROVE_OUR_DATA")}</b></a> | <a href="${githubRepoUrl}" target="_blank">GitHub</a>` 34}); 35map.addControl(attributionControl); 36 37// Get map control 38var bottomRightClass = document.getElementsByClassName("mapboxgl-ctrl-bottom-right"); 39var bottomRightControl = bottomRightClass[0]; 40 41// Add legend inside control 42bottomRightControl.insertBefore(legendContainer, bottomRightControl.firstChild); 43legendContainer.appendChild(legendTitle); 44legendContainer.appendChild(legend50plus); 45legendContainer.appendChild(legend10plus); 46legendContainer.appendChild(legend3plus); 47legendContainer.appendChild(legendLess3); 48// legendContainer.appendChild(legendUndetermined); 49 50const $showPropertiesLess3 = document.getElementById('showPropertiesLess3') 51$showPropertiesLess3.addEventListener("change", showPropertiesProcessFilter); 52 53 54// View Filtering 55function getViewFilters () { 56 return showPropertiesLess3.checked ? [] : [['>', ownedColumn, 2]] 57} 58 59function showPropertiesProcessFilter () { 60 map.setFilter("allProperties", ['all', 61 ...getViewFilters() 62 ]); 63} 64 65// Add navigation 66var navigationControl = new mapboxgl.NavigationControl(); 67map.addControl(navigationControl, "top-right"); 68 69let flex 70let searchIndex = {} 71 72async function getSearchIndex () { 73 const searchIndexResponse = await fetch(searchIndexUrl) 74 const searchIndexJSON = await searchIndexResponse.json() 75 searchIndex = searchIndexJSON.searchIndex 76 77 flex = new FlexSearch({ 78 encode: "simple", 79 profile: "fast", 80 // async: true, 81 doc: { 82 id: propertyIndexColumn, 83 field: ["search"] 84 }, 85 }) 86 87 console.log('flex.add') 88 await flex.add(searchIndex) 89 console.log('flex.add done') 90} 91 92map.on("load", function() { 93 // Set source data 94 map.addSource("propertyData", { 95 type: "vector", 96 maxzoom: 14, // Allows overzoom 97 tiles: [tiles], 98 promoteId: propertyIndexColumn 99 }); 100 101 map.addLayer({ 102 "id": "allProperties", 103 "type": "circle", 104 "source": "propertyData", 105 "source-layer": "features", 106 "paint": { 107 "circle-radius": defaultRadius, 108 "circle-color": defaultColors, 109 "circle-opacity": defaultOpacity, 110 "circle-stroke-width": 1, 111 "circle-stroke-color": "rgba(0, 0, 0, .25)", 112 }, 113 'filter': ['all', 114 ...getViewFilters() 115 ] 116 }); 117 118 // Disable functionality if IE 119 if (checkIE() == true) { 120 // Show unsupported message 121 searchInput.value = i18next.t('BROWSER_WARNING'); 122 searchInput.disabled = true; 123 searchInputContainer.style.display = "block"; 124 } else { 125 // Remove persisted value 126 searchInput.value = ""; 127 // Show search 128 searchInputContainer.style.display = "flex"; 129 130 // Add input listeners 131 searchInput.addEventListener("keypress", e => { 132 if (e.key === 'Enter') { 133 matchAddresses(searchInput.value) 134 } 135 }); 136 searchButton.addEventListener("click", () => { 137 if (searchInput.value) { 138 matchAddresses(searchInput.value) 139 } 140 }); 141 142 // Allow hover and click 143 setHoverState("propertyData", "features", "allProperties"); 144 }; 145 146 getSearchIndex() 147 148 // Hide spinner 149 spinner.style.display = "none"; 150}); 151 152map.on("error", function(e) { 153 // Don't log empty tile errors 154 if (e && e.error.status != 403) { 155 console.error(e); 156 }; 157}); 158 159function addLayer (name, data, radius, color, opacity) { 160 // Set source data 161 map.addSource(name, { 162 type: "geojson", 163 data: data, 164 promoteId: propertyIndexColumn 165 }); 166 167 // Add to map 168 map.addLayer({ 169 "id": name, 170 "type": "circle", 171 "source": name, 172 "paint": { 173 "circle-radius": radius, 174 "circle-color": color, 175 "circle-opacity": opacity, 176 "circle-stroke-width": 2, 177 "circle-stroke-color": "rgba(0, 0, 0, .25)", 178 }, 179 }); 180 181 // Style hover 182 setHoverState(data, null, name); 183}; 184 185function setHoverState (sourceData, sourceLayer, hoverLayer) { 186 // Building under cursor 187 var buildingAtPoint = null; 188 // Declared here to fix duplicates 189 var buildingID = null; 190 191 map.on("mousemove", hoverLayer, function(e) { 192 var featuresAtPoint = map.queryRenderedFeatures(e.point, { layers: [hoverLayer] }); 193 if (sourceLayer != null) { 194 // Vector source 195 buildingAtPoint = getBuildingAtPoint(featuresAtPoint, sourceData); 196 } else { 197 // GeoJSON source 198 buildingAtPoint = getBuildingAtPoint(featuresAtPoint, hoverLayer); 199 }; 200 201 if (buildingAtPoint) { 202 if ( 203 selectedContainer.style.display !== "block" && 204 spinner.style.display !== "block" 205 ) { 206 searchInput.value = buildingAtPoint.properties[propertyAddressColumn]; 207 } 208 map.getCanvas().style.cursor = "pointer"; 209 // Remove existing state 210 if (buildingID) { 211 if (sourceLayer != null) { 212 // Vector source 213 map.removeFeatureState({ 214 source: sourceData, 215 sourceLayer: sourceLayer 216 }); 217 } else { 218 // GeoJSON source 219 map.removeFeatureState({ 220 source: hoverLayer, 221 id: buildingID 222 }); 223 }; 224 }; 225 226 // Set new ID 227 buildingID = featuresAtPoint[0].properties[propertyIndexColumn]; 228 229 // Hover to true 230 if (sourceLayer != null) { 231 // Vector source 232 map.setFeatureState({ 233 source: sourceData, 234 sourceLayer: sourceLayer, 235 id: buildingID 236 }, { 237 hover: true 238 }); 239 } else { 240 // GeoJSON source 241 map.setFeatureState({ 242 source: hoverLayer, 243 id: buildingID 244 }, { 245 hover: true 246 }); 247 }; 248 } else { 249 // Clear var 250 buildingAtPoint = null; 251 }; 252 }); 253 254 map.on("click", hoverLayer, function(e) { 255 // Hover to false 256 if (buildingID) { 257 if (sourceLayer != null) { 258 // Vector source 259 map.setFeatureState({ 260 source: sourceData, 261 sourceLayer: sourceLayer, 262 id: buildingID 263 }, { 264 hover: false 265 }); 266 } else { 267 // GeoJSON source 268 map.setFeatureState({ 269 source: hoverLayer, 270 id: buildingID 271 }, { 272 hover: false 273 }); 274 }; 275 }; 276 277 // Select property 278 if (buildingAtPoint) { 279 // Reset UI 280 resetMap(); 281 // Update it 282 renderSelectedUI(buildingAtPoint); 283 // Log event 284 firebase.analytics().logEvent("map-point-clicked", { 285 property_address: buildingAtPoint.properties[propertyAddressColumn], 286 taxpayer: buildingAtPoint.properties[taxpayerColumn], 287 affiliated_with: buildingAtPoint.properties[affiliatedWithColumn], 288 }); 289 }; 290 }); 291 292 map.on("mouseleave", hoverLayer, function() { 293 if ( 294 selectedContainer.style.display !== "block" && 295 spinner.style.display !== "block" 296 ) { 297 searchInput.value = '' 298 } 299 // Hover to false 300 if (buildingID) { 301 if (sourceLayer != null) { 302 // Vector source 303 map.setFeatureState({ 304 source: sourceData, 305 sourceLayer: sourceLayer, 306 id: buildingID 307 }, { 308 hover: false 309 }); 310 } else { 311 // GeoJSON source 312 map.setFeatureState({ 313 source: hoverLayer, 314 id: buildingID 315 }, { 316 hover: false 317 }); 318 }; 319 }; 320
321 // Clear var 322 buildingID = null; 323 324 // Restore cursor 325 map.getCanvas().style.cursor = ""; 326 }); 327}; 328 329function getBuildingAtPoint (features, source) { 330 var filtered = features.filter(function(feature) { 331 var pointSource = feature.layer.source; 332 // Return feature when trimmed input is found in buildings array 333 return pointSource.indexOf(source) > -1; 334 }); 335 return filtered[0]; 336};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.