Skip to content
Snippets Groups Projects
Commit 46013a50 authored by Lene Wasskog's avatar Lene Wasskog
Browse files

feat: Populate poi select list and get geojson using javascript, some refactoring

parent 932f143e
Branches
No related tags found
1 merge request!191Add map module and Open-Meteo support
......@@ -19,21 +19,13 @@ package no.nibio.vips.logic.service;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
......@@ -48,9 +40,8 @@ import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.webcohesion.enunciate.metadata.Facet;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import java.util.Arrays;
import java.util.stream.Collectors;
import javax.ws.rs.QueryParam;
import no.nibio.vips.gis.GISUtil;
import no.nibio.vips.logic.entity.Country;
......@@ -109,7 +100,6 @@ public class POIService {
@Consumes("application/json;charset=UTF-8")
@Produces("application/json;charset=UTF-8")
public Response postPoi(String poiJson) {
// TODO Fix authentication
ObjectMapper oM = new ObjectMapper();
Map<Object, Object> poiMap;
try {
......@@ -119,17 +109,14 @@ public class POIService {
return Response.status(Status.BAD_REQUEST).entity("Unable to parse input").build();
}
Integer poiUserId = poiMap.get("userId") != null ? Integer.parseInt(poiMap.get("userId").toString()) : null;
VipsLogicUser user = SessionControllerGetter.getUserBean().getVipsLogicUser(poiUserId);
VipsLogicUser user = SessionControllerGetter.getUserBean().getUserFromUUID(httpServletRequest);
if (user == null) {
LOGGER.error("No user found for userId={}", poiUserId);
LOGGER.error("No user found for UUID in Authorization");
return Response.status(Status.UNAUTHORIZED).build();
}
LOGGER.error("Remember to check for roles as well, if necessary!");
PointOfInterestBean poiBean = SessionControllerGetter.getPointOfInterestBean();
Integer poiTypeId = poiMap.get("typeId") != null ? Integer.parseInt(poiMap.get("typeId").toString()) : null;
if(poiTypeId == null) {
return Response.status(Status.BAD_REQUEST).entity("Point of interest type is required").build();
......@@ -154,7 +141,6 @@ public class POIService {
Point p3d = gisUtil.createPointWGS84(coordinate);
poiToSave.setGisGeom(p3d);
}
poiToSave = poiBean.storePoi(poiToSave);
if (poiToSave != null) {
......@@ -189,6 +175,19 @@ public class POIService {
return Response.ok().entity(SessionControllerGetter.getPointOfInterestBean().getPoisAsGeoJson(pois)).build();
}
@POST
@Path("geojson")
@Produces("application/json;charset=UTF-8")
@Consumes(MediaType.APPLICATION_JSON)
public Response getPoisGeoJson(Set<Integer> ids) {
// Retrieve POIs from data source
List<PointOfInterest> pois = SessionControllerGetter.getPointOfInterestBean().getPois(ids);
if (pois == null || pois.isEmpty()) {
return Response.noContent().build();
}
return Response.ok(SessionControllerGetter.getPointOfInterestBean().getPoisAsGeoJson(pois)).build();
}
/**
* Find a POI (Point of interest) by name
*
......
.modal {
.map-modal {
display: none;
position: fixed;
z-index: 1000;
......@@ -10,7 +10,7 @@
background-color: rgba(0, 0, 0, 0.9);
}
.modal-content {
.map-modal-content {
position: relative;
height: 100%;
width: 100%;
......
......@@ -16,19 +16,18 @@ import {
class MapModal {
/**
* @param mapContainerId The id of the HTML element to which the map should be added
* @param mapModalId The id of the HTML element in which the modal should be opened
* @param typeNameMap A mapping from pointOfInterestTypeIds to their localized names
* @param geoJsonData GeoJson containing all features which should be displayed on the map
* @param allowNewPoints Whether or not the user should be allowed to add new points
* @param callbackOnPersistNew Callback function for persisting newly created point
* @param callbackOnClose Callback function to call when closing the modal
*/
constructor(mapContainerId, typeNameMap, geoJsonData, allowNewPoints = false, callbackOnPersistNew = null, callbackOnClose = null) {
this.mapContainerId = mapContainerId;
constructor(mapModalId, typeNameMap, geoJsonData, allowNewPoints = false, callbackOnClose = null) {
this.mapModalId = mapModalId;
this.mapContainerId = mapModalId + "-container";
this.typeNameMap = typeNameMap;
this.geoJsonData = geoJsonData;
this.allowNewPoints = allowNewPoints;
this.callbackOnPersistNew = callbackOnPersistNew;
this.callbackOnClose = callbackOnClose;
this.map = null;
......@@ -57,6 +56,7 @@ class MapModal {
tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19
}).addTo(this.map);
console.info("Create map " + this.mapContainerId + " with points", this.geoJsonData);
this.setUpSelectedPointInfoPanel();
this.setUpZoomToCurrentLocation();
......@@ -208,19 +208,15 @@ class MapModal {
confirmSelection(feature) {
console.info("Confirm selection", feature);
let poiId = feature.properties.pointOfInterestId;
if (!poiId && typeof this.callbackOnPersistNew === 'function') {
if (typeof this.callbackOnClose === 'function') {
const pointData = {
id: feature.properties.pointOfInterestId,
name: feature.properties.pointOfInterestName,
typeId: feature.properties.pointOfInterestTypeId,
longitude: feature.geometry.coordinates[0],
latitude: feature.geometry.coordinates[1]
};
console.info("Persist new", pointData);
this.callbackOnPersistNew(pointData);
}
if (typeof this.callbackOnClose === 'function') {
this.callbackOnClose(poiId);
this.callbackOnClose(pointData);
console.info("Goodbye from map modal!")
}
this.closeModal();
......@@ -423,15 +419,20 @@ class MapModal {
}
openModal(selectedPointOfInterestId) {
document.getElementById(this.mapModalId).style.display = 'block';
this.initMap();
if(selectedPointOfInterestId) {
this.selectPointById(selectedPointOfInterestId);
}
document.getElementById('mapModal').style.display = 'block';
this.initMap();
}
closeModal() {
document.getElementById('mapModal').style.display = 'none';
document.getElementById(this.mapModalId).style.display = 'none';
if (this.map) {
console.info("Remove map");
this.map.remove();
this.map = null;
}
}
}
......
......@@ -37,73 +37,85 @@
</script>
<script type="module">
import MapModal from '/js/mapModal.js';
function callbackPersistNewPoint(pointData) {
const userId = ${user.userId};
const params = {
'name': pointData.name,
'typeId': pointData.typeId,
'longitude': pointData.longitude,
'latitude': pointData.latitude,
'altitude': '0', // default value - populate using a service for getting altitude for coordinates?
'userId': userId,
}
$.ajax({
url: "/rest/poi",
type: "POST",
contentType: "application/json",
data: JSON.stringify(params),
success: function(response) {
addOption(response.pointOfInterestId, response.name);
mapModalInstance.saveSuccess(response.pointOfInterestId);
console.info("Success:", response);
},
error: function(jqXHR, textStatus, errorThrown) {
console.error("Error:", textStatus, errorThrown);
}
// Read the list of locationPointOfInterest into javascript array, to be able to dynamically manipulate it
let poiList = [
<#list locationPointOfInterests as poi>
{
"pointOfInterestId": "${poi.pointOfInterestId}",
"name": "${poi.name?json_string}",
"latitude": "${poi.latitude!''}",
"longitude": "${poi.longitude!''}",
"pointOfInterestTypeId": "${poi.pointOfInterestTypeId!''}"
}<#if poi_has_next>,</#if>
</#list>
];
renderLocationPointOfInterestSelect(poiList);
function renderLocationPointOfInterestSelect(elementList, selectedId) {
let selectElement = document.querySelector('select[name="locationPointOfInterestId"]');
elementList.forEach(poi => {
let option = document.createElement('option');
option.value = poi.pointOfInterestId;
option.textContent = poi.name;
selectElement.appendChild(option);
});
selectPointOfInterest(selectedId);
}
function callbackUpdateLocationPointOfInterest(pointOfInterestId) {
const selectBox = document.querySelector('select[name="locationPointOfInterestId"]');
if(pointOfInterestId) {
function selectPointOfInterest(selectedId) {
const selectElement = document.querySelector('select[name="locationPointOfInterestId"]');
if(selectedId) {
let optionFound = false;
for (let i = 0; i < selectBox.options.length; i++) {
if (selectBox.options[i].value == pointOfInterestId) {
selectBox.selectedIndex = i; // Select the matching option
for (let i = 0; i < selectElement.options.length; i++) {
if (selectElement.options[i].value == selectedId) {
selectElement.selectedIndex = i;
optionFound = true;
break;
}
}
if (!optionFound) {
console.error("No matching option found for poi.id:", pointOfInterestId);
console.error("No matching option found for poi.id:", selectedId);
}
}
}
// TODO Ensure options are sorted alphabetically..?
function addOption(pointOfInterestId, name) {
let selectElement = document.querySelector('select[name="locationPointOfInterestId"]');
let newOption = document.createElement("option");
newOption.value = pointOfInterestId;
newOption.text = name;
selectElement.insertBefore(newOption, selectElement.firstChild);
selectElement.value = pointOfInterestId;
function persistNewPoint(pointData) {
const params = {
'name': pointData.name,
'typeId': pointData.typeId,
'longitude': pointData.longitude,
'latitude': pointData.latitude,
'altitude': '0', // default value - populate using a service for getting altitude for coordinates?
}
fetch("/rest/poi", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': '${user.userUuid}'
},
body: JSON.stringify(params)
})
.then(response => response.json())
.then(pointOfInterest => {
poiList.push(pointOfInterest);
renderLocationPointOfInterestSelect(poiList, pointOfInterest.pointOfInterestId);
console.info("Point of interest successfully persisted", pointOfInterest);
})
.catch(error => {
console.error("Unable to persist new point of interest", error);
});
}
const typeNameMap = {
0: "${i18nBundle["pointOfInterestType_0"]}",
1: "${i18nBundle["pointOfInterestType_1"]}",
2: "${i18nBundle["pointOfInterestType_2"]}",
3: "${i18nBundle["pointOfInterestType_3"]}",
5: "${i18nBundle["pointOfInterestType_5"]}",
6: "${i18nBundle["pointOfInterestType_6"]}",
7: "${i18nBundle["pointOfInterestType_7"]}"
};
const poiGeoJson = JSON.parse('${locationPointOfInterestsGeoJson?json_string}');
const stationGeoJson = JSON.parse('${weatherStationPointOfInterestsGeoJson?json_string}')
const mapModalInstance = new MapModal('mapContainer', typeNameMap, poiGeoJson, true, callbackPersistNewPoint, callbackUpdateLocationPointOfInterest);
window.mapModalInstance = mapModalInstance;
function callbackOnClose(pointOfInterestData) {
if(!pointOfInterestData.id) {
persistNewPoint(pointOfInterestData);
}
selectPointOfInterest(pointOfInterestData.id);
}
// If poi is selected, send id to map modal before opening
window.openModal = () => {
function getSelectedPointOfInterestId() {
const selectElement = document.querySelector('select[name="locationPointOfInterestId"]');
const selectedOption = selectElement.options[selectElement.selectedIndex];
......@@ -115,7 +127,37 @@
selectedPointOfInterestId = parsedValue;
}
}
window.mapModalInstance.openModal(selectedPointOfInterestId);
return selectedPointOfInterestId;
}
const typeNameMap = {
0: "${i18nBundle["pointOfInterestType_0"]}",
1: "${i18nBundle["pointOfInterestType_1"]}",
2: "${i18nBundle["pointOfInterestType_2"]}",
3: "${i18nBundle["pointOfInterestType_3"]}",
5: "${i18nBundle["pointOfInterestType_5"]}",
6: "${i18nBundle["pointOfInterestType_6"]}",
7: "${i18nBundle["pointOfInterestType_7"]}"
};
window.openModal = () => {
let poiIds = poiList.map(poi => poi.pointOfInterestId);
fetch("/rest/poi/geojson", {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(poiIds)
})
.then(response => response.json())
.then(geoJson => {
window.mapModalInstance = new MapModal('map-modal', typeNameMap, geoJson, true, callbackOnClose);
window.mapModalInstance.openModal(getSelectedPointOfInterestId());
})
.catch(error => {
console.error('Unable to convert poi list to GeoJson, cannot open map', error);
});
};
window.closeModal = () => window.mapModalInstance && window.mapModalInstance.closeModal();
......@@ -407,16 +449,13 @@
<div class="select-container" style="flex: 1; display: flex; align-items: center;">
<select class="form-control" id="locationPointOfInterestId" name="locationPointOfInterestId" onblur="validateField(this);" style="width: calc(100% - 30px);">
<option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.locationPointOfInterestId?lower_case}</option>
<#list locationPointOfInterests?sort_by("name") as poi>
<option value="${poi.pointOfInterestId}"<#if forecastConfiguration.locationPointOfInterestId?has_content && poi.pointOfInterestId == forecastConfiguration.locationPointOfInterestId.pointOfInterestId> selected="selected"</#if>>${poi.name}</option>
</#list>
</select>
<i id="open-map-modal-icon" class="fa fa-map-marker" onclick="openModal()"></i>
</div>
<div id="mapModal" class="modal">
<div class="modal-content">
<div id="map-modal" class="map-modal">
<div class="map-modal-content">
<span class="close-button" onclick="closeModal()">&times;</span>
<div id="mapContainer" style="height: 100vh; width: 100%; position: relative;"></div>
<div id="map-modal-container" style="height: 100vh; width: 100%; position: relative;"></div>
</div>
</div>
<span class="help-block" id="${formId}_locationPointOfInterestId_validation"></span>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment