import { algoliasearch } from 'algoliasearch'; const ALGOLIA_KEY = '9fb3db0222f7b5aef0e2b30791ee6201'; const INDEX_NAME = 'pubfinder'; const client = algoliasearch('YSWWVAX5RB', ALGOLIA_KEY); const $ = document.querySelector.bind(document); const algolia_params = { hitsPerPage: 1000, }; const dataController = (function () { const initialRectBounds = { north: 51.525241, south: 51.488855, east: -0.09138, west: -0.122212, }; const maxMapSpace = { north: 51.74, south: 51.27, west: -0.51, east: 0.23, }; const initialPolygonBounds = [ { lat: 51.528125, lng: -0.104853 }, { lat: 51.519405, lng: -0.089005 }, { lat: 51.508742, lng: -0.108198 }, { lat: 51.507389, lng: -0.132242 }, { lat: 51.522659, lng: -0.134998 }, ]; const centralPosition = { lat: 51.508616, lng: -0.125319 }; return { initialRectBounds, centralPosition, maxMapSpace, initialPolygonBounds, getSearchResults: async function (query = '', shape = '', bounds = []) { let geoParam; if (shape === 'rectangle') { geoParam = { insideBoundingBox: [bounds], }; } else if (shape === 'polygon') { geoParam = { insidePolygon: [bounds], }; } else { geoParam = { aroundLatLng: bounds, // aroundRadius: 1000, }; } const res = await client.searchSingleIndex({ indexName: INDEX_NAME, searchParams: { query, ...algolia_params, ...geoParam, attributesToRetrieve: ['_geoloc', 'name'], }, }); return res; }, getCurrentQuery: function () { return document.querySelector('#searchbar').value; }, }; })(); const interfaceController = (function () { let markers = []; let shape; let map; const DOMStrings = { selectRect: '.select--rectangle', selectPoly: '.select--polygon', ip: '.select--ip', ipLoader: '.ip-loader', hits: '#hits', searchBar: '#searchbar', }; const generateMap = async function (bounds, position, maxSpace, initialPolygonBounds, currentShape, zoom = 12) { console.log(bounds, position, maxSpace, initialPolygonBounds, currentShape, zoom); const { Map, Polygon, Rectangle } = await google.maps.importLibrary('maps'); map = new Map(document.getElementById('map'), { zoom, center: position, mapId: 'pub_map', restriction: { latLngBounds: maxSpace, strictBounds: true, }, }); if (currentShape === 'rectangle') { shape = new Rectangle({ bounds: bounds, editable: true, draggable: true, }); shape.setMap(map); } else if (currentShape === 'polygon') { shape = new Polygon({ paths: initialPolygonBounds, strokeColor: '#FF0000', strokeOpacity: 0.8, strokeWeight: 2, fillColor: '#FF0000', fillOpacity: 0.35, editable: true, draggable: true, geodesic: true, }); shape.setMap(map); } }; return { handleShapeSelect: function (e) {}, initMap: generateMap, embedSearchResults: async function (hits) { const { AdvancedMarkerElement } = await google.maps.importLibrary('marker'); const { InfoWindow } = await google.maps.importLibrary('maps'); hits.forEach((hit) => { const marker = new AdvancedMarkerElement({ map: map, position: { lat: hit._geoloc.lat, lng: hit._geoloc.lng, }, }); const window = new InfoWindow({ content: hit.name, }); marker.addListener('click', function () { window.open({ anchor: marker, map, }); }); markers.push(marker); }); }, updateHitsList: function (hits) { const slicedArr = hits.slice(0, 20); $(DOMStrings.hits).innerHTML = ''; let html = ``; $(DOMStrings.hits).insertAdjacentHTML('beforeend', html); }, handleBoundChange: function () { const { south, west, north, east } = shape.getBounds()?.toJSON(); return [north, east, south, west]; }, getActiveShape: function () { return shape; }, getShapeBounds: function () { if (shape instanceof google.maps.Polygon) { const paths = shape.getPaths(); let points = []; paths.forEach((path) => { path.forEach((latLng) => { const lat = latLng.lat(); const lng = latLng.lng(); points.push(lat, lng); }); }); return points; } else { const { south, west, north, east } = shape.getBounds()?.toJSON(); return [north, east, south, west]; } }, removeMarkers: function () { for (let i = 0; i < markers.length; i++) { markers[i].setMap(null); } }, getDOMStrings: function () { return DOMStrings; }, }; })(); const controller = (function (dataCTRL, uiCTRL) { const DOM = uiCTRL.getDOMStrings(); const rectBounds = dataCTRL.initialRectBounds; const centralPoint = dataCTRL.centralPosition; const mapBounds = dataCTRL.maxMapSpace; // This is rectangle bounds const initialPolygonBounds = dataCTRL.initialPolygonBounds; // Polygon bound const initialSetup = async function () { await uiCTRL.initMap(rectBounds, centralPoint, mapBounds, initialPolygonBounds, 'rectangle', 12); const searchResults = await dataCTRL.getSearchResults('', 'rectangle', [ rectBounds.north, rectBounds.east, rectBounds.south, rectBounds.west, ]); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }; const setupMapListeners = async function () { const shape = uiCTRL.getActiveShape(); if (shape instanceof google.maps.Rectangle) { let debounceTimer; shape.addListener('bounds_changed', () => { uiCTRL.removeMarkers(); clearTimeout(debounceTimer); debounceTimer = setTimeout(async () => { const newAlgoliaBounds = uiCTRL.handleBoundChange(); const searchResults = await dataCTRL.getSearchResults( dataCTRL.getCurrentQuery(), 'rectangle', newAlgoliaBounds ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }, 200); }); } if (shape instanceof google.maps.Polygon) { let debounceTimer; const paths = shape.getPaths(); const handlePathDrag = async function () { uiCTRL.removeMarkers(); clearTimeout(debounceTimer); debounceTimer = setTimeout(async () => { const points = uiCTRL.getShapeBounds(); const searchResults = await dataCTRL.getSearchResults(dataCTRL.getCurrentQuery(), 'polygon', points); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }, 300); }; paths.forEach((path) => { path.addListener('set_at', handlePathDrag); path.addListener('insert_at', handlePathDrag); }); } }; const setupShapeSelectListeners = async function () { $(DOM.selectRect).addEventListener('click', async () => { await uiCTRL.initMap(rectBounds, centralPoint, mapBounds, initialPolygonBounds, 'rectangle', 12); const searchResults = await dataCTRL.getSearchResults(dataCTRL.getCurrentQuery(), 'rectangle', [ rectBounds.north, rectBounds.east, rectBounds.south, rectBounds.west, ]); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); await setupMapListeners(); }); $(DOM.selectPoly).addEventListener('click', async () => { await uiCTRL.initMap(rectBounds, centralPoint, mapBounds, initialPolygonBounds, 'polygon', 12); const translatedPolyBounds = initialPolygonBounds.flatMap(({ lat, lng }) => [lat, lng]); const searchResults = await dataCTRL.getSearchResults( dataCTRL.getCurrentQuery(), 'polygon', translatedPolyBounds ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); await setupMapListeners(); }); $(DOM.ip).addEventListener('click', async () => { $(DOM.ipLoader).classList.replace('hidden', 'inline-block'); navigator.geolocation.getCurrentPosition(async (pos) => { const position = { lat: pos.coords.latitude, lng: pos.coords.longitude }; await uiCTRL.initMap('', position, rectBounds, '', 'ip', 10); const searchResults = await dataCTRL.getSearchResults( dataCTRL.getCurrentQuery(), 'ip', `${pos.coords.latitude}, ${pos.coords.longitude}` ); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); $(DOM.ipLoader).classList.replace('inline-block', 'hidden'); }); }), $(DOM.searchBar).addEventListener('input', async (e) => { const q = dataCTRL.getCurrentQuery(); if (q > 0 && q < 3) return; const bounds = uiCTRL.getShapeBounds(); const activeShape = uiCTRL.getActiveShape(); const searchResults = await dataCTRL.getSearchResults( q, activeShape instanceof google.maps.Polygon ? 'polygon' : 'rectangle', bounds ); uiCTRL.removeMarkers(); await uiCTRL.embedSearchResults(searchResults.hits); uiCTRL.updateHitsList(searchResults.hits); }); }; return { init: async function () { await initialSetup(); await setupMapListeners(); await setupShapeSelectListeners(); }, }; })(dataController, interfaceController); controller.init();