PageSourceSearch

https://findmylandlord.netlify.app/assets/js/mapbox.js

js findmylandlord.netlify.app collected 2026-10-03 10:44:19 UTC 8,567 bytes, 336 lines download raw bytes

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.