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