From 241c3d16264f06f2c25ee587bd35d6da0c6ec944 Mon Sep 17 00:00:00 2001 From: lewa <lene.wasskog@nibio.no> Date: Tue, 27 Aug 2024 12:44:52 +0200 Subject: [PATCH] feat: Zoom to me, coordinates for new point --- src/main/webapp/css/mapModal.css | 9 +- src/main/webapp/js/mapModal.js | 212 +++++++++++------- .../templates/forecastConfigurationForm.ftl | 16 +- 3 files changed, 148 insertions(+), 89 deletions(-) diff --git a/src/main/webapp/css/mapModal.css b/src/main/webapp/css/mapModal.css index 014c138e..2026c2e1 100644 --- a/src/main/webapp/css/mapModal.css +++ b/src/main/webapp/css/mapModal.css @@ -31,6 +31,13 @@ z-index: 1100; } +#zoomToMyLocationButton { + position: absolute; + top: 10px; + left: 50px; + z-index: 1100; +} + #confirmButton { margin-top: 10px; } @@ -59,7 +66,7 @@ color: #ddd; } -#pointForm { +#newPointForm { z-index: 1200; position: absolute; } diff --git a/src/main/webapp/js/mapModal.js b/src/main/webapp/js/mapModal.js index 6a7a4e34..e7e0e068 100644 --- a/src/main/webapp/js/mapModal.js +++ b/src/main/webapp/js/mapModal.js @@ -3,7 +3,9 @@ import { map, tileLayer, geoJSON, - circleMarker, GeoJSON, + circleMarker, + marker, + GeoJSON, DomEvent } from 'https://unpkg.com/leaflet/dist/leaflet-src.esm.js'; @@ -56,7 +58,8 @@ class MapModal { maxZoom: 19 }).addTo(this.map); - this.createSelectedPointInfo(); + this.setUpSelectedPointInfoPanel(); + this.setUpZoomToCurrentLocation(); // Add points to the map geoJSON(this.geoJsonData, { @@ -90,7 +93,7 @@ class MapModal { /** * Create information panel for selected point, initially hidden. */ - createSelectedPointInfo() { + setUpSelectedPointInfoPanel() { const selectedPointInfoHtml = ` <div id="selectedPointInfo" style="display: none;"> <div id="infoMessage"></div> @@ -99,6 +102,32 @@ class MapModal { document.getElementById(this.mapContainerId).insertAdjacentHTML('beforeend', selectedPointInfoHtml); } + setUpZoomToCurrentLocation() { + const zoomButtonHtml = `<button id="zoomToMyLocationButton" class="btn btn-primary">Zoom til meg</button>`; + document.getElementById(this.mapContainerId).insertAdjacentHTML('beforeend', zoomButtonHtml); + + let zoomButton = document.getElementById('zoomToMyLocationButton') + DomEvent.disableClickPropagation(zoomButton); + zoomButton.addEventListener('click', () => { + if (navigator.geolocation) { + navigator.geolocation.getCurrentPosition((position) => { + const latitude = position.coords.latitude; + const longitude = position.coords.longitude; + + this.map.setView([latitude, longitude], 13); + + // Add a marker at the user's location + const userLocationMarker = marker([latitude, longitude]).addTo(this.map); + }, (error) => { + console.error('Geolocation failed: ' + error.message); + alert('Unable to retrieve your location.'); + }); + } else { + alert('Geolocation is not supported by this browser.'); + } + }); + } + /** * Display the panel which contains information about the currently selected point, * and a button to bring you back to the original page. @@ -129,12 +158,7 @@ class MapModal { } getFeatureById(pointOfInterestId) { - let feature = this.geoJsonData.features.find(feature => feature.properties.pointOfInterestId == pointOfInterestId); - if (!feature) { - console.info("Feature with id=" + pointOfInterestId + " not found, assume that the user wants the newly created", this.createdPoints[0]); - return this.createdPoints.length > 0 ? this.createdPoints[0] : null; - } - return feature; + return this.geoJsonData.features.find(feature => feature.properties.pointOfInterestId == pointOfInterestId); } getLayerById(pointOfInterestId) { @@ -148,12 +172,6 @@ class MapModal { }); } }); - if (!result) { - console.info("Layer with id=" + pointOfInterestId + " not found, assume that the user wants the newly created", this.createdPointLayer); - this.createdPointLayer.eachLayer((layer) => { - result = layer; - }); - } return result; } @@ -213,49 +231,41 @@ class MapModal { // If a form already exists, remove it this.closeNewPointFormIfOpen(); + // Calculate the pixel position from the map's click event const containerPoint = this.map.latLngToContainerPoint(latlng); + const newPointFormElement = this.addHtmlElementNewPointForm(containerPoint.x, containerPoint.y, latlng.lat, latlng.lng) - // Show form for creating a new point - const formHtml = ` - <div id="pointForm" class="panel panel-default" style="top: ${containerPoint.y}px; left: ${containerPoint.x}px;"> - <div class="panel-heading"> - <h3 class="panel-title">Create new point</h3> - </div> - <div class="panel-body"> - <div class="form-group"> - <label for="name">Name:</label> - <input type="text" class="form-control" id="name" name="name" autofocus> - </div> - <div class="form-group"> - <label for="poiTypeSelect">Type:</label> - <select class="form-control" id="poiTypeSelect" name="poiTypeSelect"> - <option value="2">${this.typeNameMap[2]}</option> - <option value="3">${this.typeNameMap[3]}</option> - <option value="5">${this.typeNameMap[5]}</option> - </select> - </div> - <div class="form-group text-right"> - <button id="savePointButton" class="btn btn-primary">Save</button> - </div> - </div> - </div>`; - document.getElementById(this.mapContainerId).insertAdjacentHTML('beforeend', formHtml); - - const formElement = document.getElementById('pointForm'); - DomEvent.disableClickPropagation(formElement); - + // Click inside the form should not propagate to underlying map + DomEvent.disableClickPropagation(newPointFormElement); // Add event listener to close the form if clicked outside document.addEventListener('click', this.handleClickOutsidePointForm.bind(this), true); - document.getElementById('savePointButton').addEventListener('click', () => { - this.savePoint(latlng.lat, latlng.lng); + const nameInput = newPointFormElement.querySelector('#name'); + const latitudeInput = newPointFormElement.querySelector('#latitude'); + const longitudeInput = newPointFormElement.querySelector('#longitude'); + const typeInput = newPointFormElement.querySelector('#type'); + const submitButton = newPointFormElement.querySelector('#submit-button'); + + const validateInputs = () => { + const isValidLat = !isNaN(parseFloat(latitudeInput.value)) && isFinite(latitudeInput.value); + const isValidLng = !isNaN(parseFloat(longitudeInput.value)) && isFinite(longitudeInput.value); + submitButton.disabled = !(isValidLat && isValidLng); + }; + latitudeInput.addEventListener('input', validateInputs); + longitudeInput.addEventListener('input', validateInputs); + validateInputs(); + + submitButton.addEventListener('click', () => { + this.removeExistingNewPoint(); + this.setNewPointAsSelected(nameInput.value, parseFloat(latitudeInput.value), parseFloat(longitudeInput.value), parseInt(typeInput.value, 10)); + newPointFormElement.remove(); }); }); } handleClickOutsidePointForm(event) { - const formElement = document.getElementById('pointForm'); + const formElement = document.getElementById('newPointForm'); // If the clicked element is not inside the form, close the form if (formElement && !formElement.contains(event.target)) { @@ -264,7 +274,7 @@ class MapModal { } closeNewPointFormIfOpen() { - const formElement = document.getElementById('pointForm'); + const formElement = document.getElementById('newPointForm'); if (formElement) { formElement.remove(); } @@ -273,16 +283,17 @@ class MapModal { document.removeEventListener('click', this.handleClickOutsidePointForm.bind(this), true); } - createFeatureForPoint(lng, lat, poiName, poiType) { + createFeatureForPoint(longitude, latitude, name, type) { + console.info("Create feature for [" + longitude + "," + latitude + "," + name + "," + type + "]"); return { "type": "Feature", "geometry": { "type": "Point", - "coordinates": [lng, lat] + "coordinates": [longitude, latitude] }, "properties": { - "pointOfInterestName": poiName, - "pointOfInterestTypeId": poiType + "pointOfInterestName": name, + "pointOfInterestTypeId": type } }; } @@ -307,33 +318,21 @@ class MapModal { }).addTo(this.map); } - savePoint(lat, lng) { - const poiNameElement = document.getElementById('name'); - const poiTypeElement = document.getElementById("poiTypeSelect") - - if (poiNameElement && poiTypeElement) { - const poiName = poiNameElement.value; - const poiType = parseInt(poiTypeElement.value, 10); - - // There should only be one newly created point available - if (this.createdPointLayer) { - this.map.removeLayer(this.createdPointLayer); - } - if (this.createdPoints.length > 0) { - this.createdPoints.pop(); - } - - const newPoint = this.createFeatureForPoint(lng, lat, poiName, poiType); - this.createdPoints.push(newPoint); - this.createdPointLayer = this.addNewPointToMap(newPoint); - this.createdPointLayer.eachLayer((layer) => { - this.selectPoint(newPoint, layer); - }); + setNewPointAsSelected(name, latitude, longitude, type) { + const feature = this.createFeatureForPoint(longitude, latitude, name, type); + this.createdPoints.push(feature); + this.createdPointLayer = this.addNewPointToMap(feature); + this.createdPointLayer.eachLayer((layer) => { + this.selectPoint(feature, layer); + }); + } - const formElement = document.getElementById('pointForm'); - if (formElement) { - formElement.remove(); - } + removeExistingNewPoint(){ + if (this.createdPointLayer) { + this.map.removeLayer(this.createdPointLayer); + } + if (this.createdPoints.length > 0) { + this.createdPoints.pop(); } } @@ -345,6 +344,54 @@ class MapModal { </div>`; } + /** + * Creates the HTML form for adding a new point, and add it to the map container. + * + * @param positionX Where to place the form on the x axis + * @param positionY Where to place the form on the y axis + * @param latitude Latitude of the point clicked + * @param longitude Longitude of the point clicked + * @returns {Element} + */ + addHtmlElementNewPointForm(positionX, positionY, latitude, longitude) { + const html = ` + <div id="newPointForm" class="panel panel-default" style="top: ${positionY}px; left: ${positionX}px;"> + <div class="panel-heading"> + <h3 class="panel-title">Opprett nytt sted</h3> + </div> + <div class="panel-body"> + <div class="form-group"> + <label for="name">Navn:</label> + <input type="text" class="form-control" id="name" name="name"> + </div> + <div class="form-group"> + <label for="latitude">Breddegrad:</label> + <input type="text" class="form-control" id="latitude" name="latitude" value="${latitude}"> + </div> + <div class="form-group"> + <label for="longitude">Lengdegrad:</label> + <input type="text" class="form-control" id="longitude" name="longitude" value="${longitude}"> + </div> + <div class="form-group"> + <label for="poiTypeSelect">Type:</label> + <select class="form-control" id="type" name="type"> + <option value="2">${this.typeNameMap[2]}</option> + <option value="3">${this.typeNameMap[3]}</option> + <option value="5">${this.typeNameMap[5]}</option> + </select> + </div> + <div class="form-group text-right"> + <button id="submit-button" class="btn btn-primary">Save</button> + </div> + </div> + </div>`; + const tmpContainer = document.createElement("div"); + tmpContainer.innerHTML = html; + const htmlElement = tmpContainer.querySelector('#newPointForm'); + document.getElementById(this.mapContainerId).appendChild(htmlElement); + return htmlElement; + } + /** * Function is called when newly created point of interest is successfully persisted to database. * @param pointOfInterestId @@ -368,11 +415,10 @@ class MapModal { console.info("this.geoJsonData.features", this.geoJsonData.features); } - setSelectedLocation(selectedValue) { - this.selectPointById(selectedValue); - } - - openModal(points) { + openModal(selectedPointOfInterestId) { + if(selectedPointOfInterestId) { + this.selectPointById(selectedPointOfInterestId); + } document.getElementById('mapModal').style.display = 'block'; this.initMap(); } diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl index b93f95c2..a33ab98a 100755 --- a/src/main/webapp/templates/forecastConfigurationForm.ftl +++ b/src/main/webapp/templates/forecastConfigurationForm.ftl @@ -101,13 +101,20 @@ const mapModalInstance = new MapModal('mapContainer', typeNameMap, poiGeoJson, true, callbackPersistNewPoint, callbackUpdateLocationPointOfInterest); window.mapModalInstance = mapModalInstance; + // If poi is selected, send id to map modal before opening window.openModal = () => { const selectElement = document.querySelector('select[name="locationPointOfInterestId"]'); const selectedOption = selectElement.options[selectElement.selectedIndex]; - if(selectedOption.value && selectedOption.value !== '-1') { - window.mapModalInstance.setSelectedLocation(selectedOption.value); + + let selectedPointOfInterestId; + const value = selectedOption.value; + if(value) { + const parsedValue = parseInt(value, 10); + if (!isNaN(parsedValue) && parsedValue > 0) { + selectedPointOfInterestId = parsedValue; + } } - window.mapModalInstance.openModal(); + window.mapModalInstance.openModal(selectedPointOfInterestId); }; window.closeModal = () => window.mapModalInstance && window.mapModalInstance.closeModal(); @@ -362,8 +369,7 @@ <div id="mapModal" class="modal"> <div class="modal-content"> <span class="close-button" onclick="closeModal()">×</span> - <div id="mapContainer" style="height: 100vh; width: 100%; position: relative;"> - </div> + <div id="mapContainer" style="height: 100vh; width: 100%; position: relative;"></div> </div> </div> <span class="help-block" id="${formId}_locationPointOfInterestId_validation"></span> -- GitLab