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 londonBounds = { north: 51.532, south: 51.478, east: -0.072, west: -0.16, }; 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 { londonBounds, centralPosition, maxMapSpace, initialPolygonBounds, getSearchResults: async function (shape, bounds) { let geoParam; if (shape === "rectangle") { geoParam = { insideBoundingBox: [bounds], }; } else { geoParam = { insidePolygon: [bounds], }; } const res = await client.searchSingleIndex({ indexName: INDEX_NAME, searchParams: { ...algolia_params, ...geoParam, attributesToRetrieve: ["_geoloc", "name"], }, }); return res; }, }; })(); const interfaceController = (function () { let markers = []; let shape; let map; const DOMStrings = { selectRect: ".select--rectangle", selectPoly: ".select--polygon", }; const generateMap = async function ( bounds, position, maxSpace, initialPolygonBounds, currentShape ) { const { Map, Polygon, Rectangle } = await google.maps.importLibrary("maps"); map = new Map(document.getElementById("map"), { zoom: 4, center: position, mapId: "pub_map", restriction: { latLngBounds: maxSpace, strictBounds: true, }, }); if (currentShape === "rectangle") { shape = new Rectangle({ bounds: bounds, editable: true, draggable: true, }); } else { 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); }); }, handleBoundChange: function () { const { south, west, north, east } = shape.getBounds()?.toJSON(); return [north, east, south, west]; }, getActiveShape: function () { return shape; }, 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 londonBounds = dataCTRL.londonBounds; 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( londonBounds, centralPoint, mapBounds, initialPolygonBounds, "rectangle" ); const searchResults = await dataCTRL.getSearchResults("rectangle", [ londonBounds.north, londonBounds.east, londonBounds.south, londonBounds.west, ]); await uiCTRL.embedSearchResults(searchResults.hits); }; const setupMapListeners = async function () { const shape = uiCTRL.getActiveShape(); if (shape instanceof google.maps.Rectangle) { shape.addListener("dragend", async () => { uiCTRL.removeMarkers(); const newAlgoliaBounds = uiCTRL.handleBoundChange(); const searchResults = await dataCTRL.getSearchResults( "rectangle", newAlgoliaBounds ); await uiCTRL.embedSearchResults(searchResults.hits); }); } if (shape instanceof google.maps.Polygon) { const paths = shape.getPaths(); let points = []; const handlePathDrag = async function () { uiCTRL.removeMarkers(); points = []; paths.forEach((path) => { path.forEach((latLng) => { const lat = latLng.lat(); const lng = latLng.lng(); points.push(lat, lng); }); }); const searchResults = await dataCTRL.getSearchResults( "polygon", points ); await uiCTRL.embedSearchResults(searchResults.hits); }; 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( londonBounds, centralPoint, mapBounds, initialPolygonBounds, "rectangle" ); const searchResults = await dataCTRL.getSearchResults("rectangle", [ londonBounds.north, londonBounds.east, londonBounds.south, londonBounds.west, ]); await uiCTRL.embedSearchResults(searchResults.hits); await setupMapListeners(); }); $(DOM.selectPoly).addEventListener("click", async () => { await uiCTRL.initMap( londonBounds, centralPoint, mapBounds, initialPolygonBounds, "polygon" ); const translatedPolyBounds = initialPolygonBounds.flatMap( ({ lat, lng }) => [lat, lng] ); const searchResults = await dataCTRL.getSearchResults( "polygon", translatedPolyBounds ); await uiCTRL.embedSearchResults(searchResults.hits); await setupMapListeners(); }); }; return { init: async function () { await initialSetup(); await setupMapListeners(); await setupShapeSelectListeners(); }, }; })(dataController, interfaceController); controller.init();