diff --git a/pom.xml b/pom.xml index 3abb26e8dc5643119967911f6a6d4fbe178c598a..7d84f0a2a0914c5480df14ac8a21822a30cb9965 100644 --- a/pom.xml +++ b/pom.xml @@ -19,18 +19,15 @@ <id>jitpack.io</id> <url>https://jitpack.io</url> </repository> - <!--repository> - <id>Hibernate Spatial repo</id> - <url>http://www.hibernatespatial.org/repository</url> - </repository--> - + </repositories> <dependencies> - <!--dependency> + <dependency> <groupId>com.github.bjornharrtell</groupId> + <!--groupId>org.wololo</groupId--> <artifactId>jts2geojson</artifactId> - <version>0.6.0</version> - </dependency--> + <version>0.9.0</version> + </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-spatial</artifactId> @@ -111,19 +108,19 @@ <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-annotations</artifactId> - <version>2.4.1</version> + <version>2.7.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> - <version>2.4.1</version> + <version>2.7.4</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> - <version>2.4.1</version> + <version>2.7.4</version> </dependency> <dependency> <groupId>log4j</groupId> @@ -148,11 +145,6 @@ <version>9.4.1211</version> <scope>provided</scope> </dependency> - <dependency> - <groupId>org.wololo</groupId> - <artifactId>jts2geojson</artifactId> - <version>0.8.0</version> - </dependency> <dependency> <groupId>javax</groupId> <artifactId>javaee-web-api</artifactId> @@ -214,8 +206,8 @@ <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> - <source>1.7</source> - <target>1.7</target> + <source>1.8</source> + <target>1.8</target> </configuration> </plugin> </plugins> diff --git a/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java b/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java index 24176ef0d6f804807c66f19479c850c63146c883..a7b6950b2bab922a2e4f79d393ff5b4548dee8e8 100644 --- a/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java +++ b/src/main/java/no/nibio/vips/logic/VIPSLogicApplication.java @@ -52,6 +52,7 @@ public class VIPSLogicApplication extends Application resources.add(no.nibio.vips.observationdata.ObservationDataService.class); resources.add(no.nibio.vips.logic.messaging.sms.SMSHandlingService.class); resources.add(no.nibio.vips.logic.modules.applefruitmoth.AppleFruitMothService.class); + resources.add(no.nibio.vips.logic.service.ObservationService.class); //resources.add(no.nibio.vips.logic.service.JacksonConfig.class); //resources.add(no.nibio.vips.coremanager.service.ManagerResourceImpl.class); } @@ -62,15 +63,14 @@ public class VIPSLogicApplication extends Application * given list with all resources defined in the project. */ private void addRestResourceClasses(Set<Class<?>> resources) { - resources.add(no.nibio.vips.logic.messaging.sms.SMSHandlingService.class); resources.add(no.nibio.vips.logic.modules.applefruitmoth.AppleFruitMothService.class); resources.add(no.nibio.vips.logic.modules.barleynetblotch.BarleyNetBlotchModelService.class); resources.add(no.nibio.vips.logic.modules.roughage.RoughageService.class); resources.add(no.nibio.vips.logic.service.JacksonConfig.class); resources.add(no.nibio.vips.logic.service.LogicService.class); + resources.add(no.nibio.vips.logic.service.ObservationService.class); resources.add(no.nibio.vips.logic.service.VIPSMobileService.class); resources.add(no.nibio.vips.observationdata.ObservationDataService.class); - } } \ No newline at end of file diff --git a/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java b/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java index d99ae3bea75600acd8c0ece4392df3aece2d9f0d..792de65897f57f8f435fa76c44a0eb838c43ea66 100644 --- a/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java +++ b/src/main/java/no/nibio/vips/logic/controller/servlet/ObservationController.java @@ -19,11 +19,17 @@ package no.nibio.vips.logic.controller.servlet; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.Point; import java.io.File; import java.io.IOException; +import java.util.Calendar; import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.Map; +import java.util.TimeZone; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.servlet.ServletContext; @@ -35,6 +41,8 @@ import no.nibio.vips.logic.entity.CropCategory; import no.nibio.vips.logic.entity.Observation; import no.nibio.vips.logic.entity.ObservationMethod; import no.nibio.vips.logic.entity.Organism; +import no.nibio.vips.logic.entity.Organization; +import no.nibio.vips.logic.entity.PointOfInterestWeatherStation; import no.nibio.vips.logic.entity.VipsLogicRole; import no.nibio.vips.logic.entity.VipsLogicUser; import no.nibio.vips.logic.i18n.SessionLocaleUtil; @@ -72,308 +80,396 @@ public class ObservationController extends HttpServlet { VipsLogicUser user = (VipsLogicUser) request.getSession().getAttribute("user"); // Default: View message list // for everyone - if(action == null) + if(request.getServletPath().endsWith("/observation")) { - List<Observation> observations = null; - Integer statusTypeId = -1; - Integer pestOrganismId = -1; - try + if(action == null) { - statusTypeId = request.getParameter("statusTypeId") != null ? Integer.valueOf(request.getParameter("statusTypeId")) : -1; - pestOrganismId = request.getParameter("pestOrganismId") != null ? Integer.valueOf(request.getParameter("pestOrganismId")) : -1; - } - catch(NumberFormatException nfe) { - } - - - if(statusTypeId > 0) - { - observations = SessionControllerGetter.getObservationBean().getObservations(user.getOrganizationId().getOrganizationId(), statusTypeId); - } - else if(pestOrganismId > 0) - { - observations = SessionControllerGetter.getObservationBean().getObservationsOfPest(pestOrganismId); - } - else - { - observations = SessionControllerGetter.getObservationBean().getObservations(user.getOrganizationId().getOrganizationId()); + List<Observation> observations = null; + Integer statusTypeId = -1; + Integer pestOrganismId = -1; + try + { + statusTypeId = request.getParameter("statusTypeId") != null ? Integer.valueOf(request.getParameter("statusTypeId")) : -1; + pestOrganismId = request.getParameter("pestOrganismId") != null ? Integer.valueOf(request.getParameter("pestOrganismId")) : -1; + } + catch(NumberFormatException nfe) { + } + + + if(statusTypeId > 0) + { + observations = SessionControllerGetter.getObservationBean().getObservations(user.getOrganizationId().getOrganizationId(), statusTypeId); + } + else if(pestOrganismId > 0) + { + observations = SessionControllerGetter.getObservationBean().getObservationsOfPest(pestOrganismId); + } + else + { + observations = SessionControllerGetter.getObservationBean().getObservations(user.getOrganizationId().getOrganizationId()); + } + + Collections.sort(observations); + Collections.reverse(observations); + List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); + request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); + request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); + request.setAttribute("selectedPestOrganismId", pestOrganismId); + request.setAttribute("selectedStatusTypeId", statusTypeId); + request.setAttribute("observations", observations); + request.setAttribute("userHasObserverPrivilege", SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)); + request.setAttribute("userIsObservationAuthority", SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)); + // If this is a redirect from a controller, with a message to be passed on + request.setAttribute("messageKey", request.getParameter("messageKey")); + request.getRequestDispatcher("/observationList.ftl").forward(request, response); } - - Collections.sort(observations); - Collections.reverse(observations); - List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); - request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); - request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); - request.setAttribute("selectedPestOrganismId", pestOrganismId); - request.setAttribute("selectedStatusTypeId", statusTypeId); - request.setAttribute("observations", observations); - request.setAttribute("userHasObserverPrivilege", SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)); - request.setAttribute("userIsObservationAuthority", SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)); - // If this is a redirect from a controller, with a message to be passed on - request.setAttribute("messageKey", request.getParameter("messageKey")); - request.getRequestDispatcher("/observationList.ftl").forward(request, response); - } - // Create a new observation - // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER - else if(action.equals("newObservationForm")) - { - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + // Create a new observation + // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER + else if(action.equals("newObservationForm")) { - try + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) { - Observation observation = new Observation(); - request.setAttribute("observation", observation); - request.setAttribute("mapLayers", SessionControllerGetter.getUserBean().getMapLayerJSONForUser(user)); - request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); - request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); - request.setAttribute("locationPointOfInterests", SessionControllerGetter.getPointOfInterestBean().getRelevantPointOfInterestsForUser(user)); - List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); - request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); - List<Organism> allCrops = em.createNamedQuery("Organism.findAllCrops").getResultList(); - request.setAttribute("allCrops", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allCrops, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); - // Hierarchy categories - request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); - request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList()); - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + try { - request.setAttribute("statusTypeIds", em.createNamedQuery("ObservationStatusType.findAll").getResultList()); + Observation observation = new Observation(); + request.setAttribute("observation", observation); + request.setAttribute("mapLayers", SessionControllerGetter.getUserBean().getMapLayerJSONForUser(user)); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + request.setAttribute("locationPointOfInterests", SessionControllerGetter.getPointOfInterestBean().getRelevantPointOfInterestsForUser(user)); + List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); + request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); + List<Organism> allCrops = em.createNamedQuery("Organism.findAllCrops").getResultList(); + request.setAttribute("allCrops", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allCrops, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); + // Hierarchy categories + request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); + request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList()); + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + { + request.setAttribute("statusTypeIds", em.createNamedQuery("ObservationStatusType.findAll").getResultList()); + } + request.getRequestDispatcher("/observationForm.ftl").forward(request, response); + } + catch(NullPointerException | NumberFormatException ex) + { + response.sendError(500, ExceptionUtil.getStackTrace(ex)); } - request.getRequestDispatcher("/observationForm.ftl").forward(request, response); } - catch(NullPointerException | NumberFormatException ex) + else { - response.sendError(500, ExceptionUtil.getStackTrace(ex)); + response.sendError(403,"Access not authorized"); // HTTP Forbidden } } - else + // Edit an existing observation + // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER + else if(action.equals("editObservationForm")) { - response.sendError(403,"Access not authorized"); // HTTP Forbidden - } - } - // Edit an existing observation - // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER - else if(action.equals("editObservationForm")) - { - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) - { - try + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) { - Integer observationId = Integer.valueOf(request.getParameter("observationId")); - Observation observation = SessionControllerGetter.getObservationBean().getObservation(observationId);//em.find(Observation.class, observationId); - request.setAttribute("observation", observation); - request.setAttribute("mapLayers", SessionControllerGetter.getUserBean().getMapLayerJSONForUser(user)); - //System.out.println(observation.getGeoinfo()); - request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); - request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); - List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); - request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); - List<Organism> allCrops = em.createNamedQuery("Organism.findAllCrops").getResultList(); - request.setAttribute("allCrops", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allCrops, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); - // Hierarchy categories - request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); - request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList()); - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + try { - request.setAttribute("statusTypeIds", em.createNamedQuery("ObservationStatusType.findAll").getResultList()); + Integer observationId = Integer.valueOf(request.getParameter("observationId")); + Observation observation = SessionControllerGetter.getObservationBean().getObservation(observationId);//em.find(Observation.class, observationId); + request.setAttribute("observation", observation); + request.setAttribute("mapLayers", SessionControllerGetter.getUserBean().getMapLayerJSONForUser(user)); + //System.out.println(observation.getGeoinfo()); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + List<Organism> allPests = em.createNamedQuery("Organism.findAllPests").getResultList(); + request.setAttribute("allPests", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allPests, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); + List<Organism> allCrops = em.createNamedQuery("Organism.findAllCrops").getResultList(); + request.setAttribute("allCrops", SessionControllerGetter.getOrganismBean().sortOrganismsByLocalName(allCrops, SessionLocaleUtil.getCurrentLocale(request).getLanguage())); + // Hierarchy categories + request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); + request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList()); + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + { + request.setAttribute("statusTypeIds", em.createNamedQuery("ObservationStatusType.findAll").getResultList()); + } + if(request.getParameter("messageKey") != null) + { + request.setAttribute("messageKey",request.getParameter("messageKey")); + } + request.getRequestDispatcher("/observationForm.ftl").forward(request, response); } - if(request.getParameter("messageKey") != null) + catch(NullPointerException | NumberFormatException ex) { - request.setAttribute("messageKey",request.getParameter("messageKey")); + response.sendError(500, ex.getMessage() + ": " + ExceptionUtil.getStackTrace(ex)); } - request.getRequestDispatcher("/observationForm.ftl").forward(request, response); } - catch(NullPointerException | NumberFormatException ex) + else { - response.sendError(500, ex.getMessage() + ": " + ExceptionUtil.getStackTrace(ex)); + response.sendError(403,"Access not authorized"); // HTTP Forbidden } } - else - { - response.sendError(403,"Access not authorized"); // HTTP Forbidden - } - } - // Store an observation - // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER - else if(action.equals("observationFormSubmit")) - { - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + // Store an observation + // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER + else if(action.equals("observationFormSubmit")) { - try + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) { - Map<String,String[]> parameterMap; - List<FileItem> items = null; - if(ServletFileUpload.isMultipartContent(request)) + try { - // Create a factory for disk-based file items - DiskFileItemFactory factory = new DiskFileItemFactory(); + Map<String,String[]> parameterMap; + List<FileItem> items = null; + if(ServletFileUpload.isMultipartContent(request)) + { + // Create a factory for disk-based file items + DiskFileItemFactory factory = new DiskFileItemFactory(); - // Configure a repository (to ensure a secure temp location is used) - ServletContext servletContext = this.getServletConfig().getServletContext(); - File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir"); - factory.setRepository(repository); + // Configure a repository (to ensure a secure temp location is used) + ServletContext servletContext = this.getServletConfig().getServletContext(); + File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir"); + factory.setRepository(repository); - // Create a new file upload handler - ServletFileUpload upload = new ServletFileUpload(factory); + // Create a new file upload handler + ServletFileUpload upload = new ServletFileUpload(factory); - // Parse the request - items = upload.parseRequest(request); - parameterMap = FormUtil.getParameterMap(items,"UTF-8"); - } - else - { - parameterMap = request.getParameterMap(); - } - FormValidation formValidation = FormValidator.validateForm("observationForm", parameterMap, SessionLocaleUtil.getI18nBundle(request), getServletContext()); - Integer observationId = formValidation.getFormField("observationId").getValueAsInteger(); - Observation observation = observationId > 0 ? em.find(Observation.class, observationId) : new Observation(); - - - - if(formValidation.isValid()) - { - // Storing observation - // Only new observations can set the organism - if(observationId <= 0 || user.isSuperUser() || user.isOrganizationAdmin()) + // Parse the request + items = upload.parseRequest(request); + parameterMap = FormUtil.getParameterMap(items,"UTF-8"); + } + else { - observation.setOrganism(em.find(Organism.class, formValidation.getFormField("organismId").getValueAsInteger())); - observation.setCropOrganism(em.find(Organism.class, formValidation.getFormField("cropOrganismId").getValueAsInteger())); + parameterMap = request.getParameterMap(); } - //observation.setDenominator(formValidation.getFormField("denominator").getValueAsInteger()); - //observation.setObservationMethodId(em.find(ObservationMethod.class, formValidation.getFormField("observationMethodId").getWebValue())); - observation.setTimeOfObservation(formValidation.getFormField("timeOfObservation").getValueAsTimestamp()); - //observation.setObservedValue(formValidation.getFormField("observedValue").getValueAsDouble()); - observation.setUserId(user.getUserId()); - - observation.setObservationHeading(formValidation.getFormField("observationHeading").getWebValue()); - observation.setObservationText(formValidation.getFormField("observationText").getWebValue()); - observation.setObservationData(formValidation.getFormField("observationData").getWebValue()); - observation.setIsQuantified(formValidation.getFormField("isQuantified").getWebValue() != null); - observation.setBroadcastMessage(formValidation.getFormField("broadcastMessage").getWebValue() != null); + FormValidation formValidation = FormValidator.validateForm("observationForm", parameterMap, SessionLocaleUtil.getI18nBundle(request), getServletContext()); + Integer observationId = formValidation.getFormField("observationId").getValueAsInteger(); + Observation observation = observationId > 0 ? em.find(Observation.class, observationId) : new Observation(); - boolean sendNotification = false; - // Storing approval status - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + + + if(formValidation.isValid()) { - Integer statusTypeId = formValidation.getFormField("statusTypeId").getValueAsInteger(); - if( - observation.getStatusTypeId() == null - || ! observation.getStatusTypeId().equals(statusTypeId) - ) + // Storing observation + // Only new observations can set the organism + if(observationId <= 0 || user.isSuperUser() || user.isOrganizationAdmin()) { - observation.setStatusChangedByUserId(user.getUserId()); - observation.setStatusChangedTime(SystemTime.getSystemTime()); - // Status changed to approved, sending notifications - if(statusTypeId.equals(Observation.STATUS_TYPE_ID_APPROVED) && observation.getBroadcastMessage()) + observation.setOrganism(em.find(Organism.class, formValidation.getFormField("organismId").getValueAsInteger())); + observation.setCropOrganism(em.find(Organism.class, formValidation.getFormField("cropOrganismId").getValueAsInteger())); + } + //observation.setDenominator(formValidation.getFormField("denominator").getValueAsInteger()); + //observation.setObservationMethodId(em.find(ObservationMethod.class, formValidation.getFormField("observationMethodId").getWebValue())); + observation.setTimeOfObservation(formValidation.getFormField("timeOfObservation").getValueAsTimestamp()); + //observation.setObservedValue(formValidation.getFormField("observedValue").getValueAsDouble()); + observation.setUserId(user.getUserId()); + + observation.setObservationHeading(formValidation.getFormField("observationHeading").getWebValue()); + observation.setObservationText(formValidation.getFormField("observationText").getWebValue()); + observation.setObservationData(formValidation.getFormField("observationData").getWebValue()); + observation.setIsQuantified(formValidation.getFormField("isQuantified").getWebValue() != null); + observation.setBroadcastMessage(formValidation.getFormField("broadcastMessage").getWebValue() != null); + + boolean sendNotification = false; + // Storing approval status + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVATION_AUTHORITY, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + { + Integer statusTypeId = formValidation.getFormField("statusTypeId").getValueAsInteger(); + if( + observation.getStatusTypeId() == null + || ! observation.getStatusTypeId().equals(statusTypeId) + ) { - sendNotification = true; + observation.setStatusChangedByUserId(user.getUserId()); + observation.setStatusChangedTime(SystemTime.getSystemTime()); + // Status changed to approved, sending notifications + if(statusTypeId.equals(Observation.STATUS_TYPE_ID_APPROVED) && observation.getBroadcastMessage()) + { + sendNotification = true; + } } + observation.setStatusTypeId(statusTypeId); + observation.setStatusRemarks(formValidation.getFormField("statusRemarks").getWebValue()); } - observation.setStatusTypeId(statusTypeId); - observation.setStatusRemarks(formValidation.getFormField("statusRemarks").getWebValue()); - } - else if(observation.getStatusTypeId() == null) - { - observation.setStatusTypeId(Observation.STATUS_TYPE_ID_PENDING); - } - //observation.setLocation(formValidation.getFormField("location").getValueAsPointWGS84()); - //System.out.println(formValidation.getFormField("geoInfo").getWebValue()); - observation.setLocationPointOfInterestId(formValidation.getFormField("locationPointOfInterestId").getValueAsInteger() > 0 ? - formValidation.getFormField("locationPointOfInterestId").getValueAsInteger() : null); - if(formValidation.getFormField("locationPointOfInterestId").getValueAsInteger() > 0) - { - observation.setLocationPointOfInterestId(formValidation.getFormField("locationPointOfInterestId").getValueAsInteger()); - observation.setGeoinfo(null); - } - else - { - observation.setLocationPointOfInterestId(null); - observation.setGeoinfo(formValidation.getFormField("geoInfo").getWebValue()); - } - observation = SessionControllerGetter.getObservationBean().storeObservation(observation); + else if(observation.getStatusTypeId() == null) + { + observation.setStatusTypeId(Observation.STATUS_TYPE_ID_PENDING); + } + //observation.setLocation(formValidation.getFormField("location").getValueAsPointWGS84()); + //System.out.println(formValidation.getFormField("geoInfo").getWebValue()); + observation.setLocationPointOfInterestId(formValidation.getFormField("locationPointOfInterestId").getValueAsInteger() > 0 ? + formValidation.getFormField("locationPointOfInterestId").getValueAsInteger() : null); + if(formValidation.getFormField("locationPointOfInterestId").getValueAsInteger() > 0) + { + observation.setLocationPointOfInterestId(formValidation.getFormField("locationPointOfInterestId").getValueAsInteger()); + observation.setGeoinfo(null); + } + else + { + observation.setLocationPointOfInterestId(null); + observation.setGeoinfo(formValidation.getFormField("geoInfo").getWebValue()); + } + observation = SessionControllerGetter.getObservationBean().storeObservation(observation); - // Image handling - // Delete the current illustration - String deleteIllustration = formValidation.getFormField("deleteIllustration").getWebValue(); - if(deleteIllustration != null && deleteIllustration.equals("true")) - { - observation = SessionControllerGetter.getObservationBean().deleteObservationIllustration(observation); - } + // Image handling + // Delete the current illustration + String deleteIllustration = formValidation.getFormField("deleteIllustration").getWebValue(); + if(deleteIllustration != null && deleteIllustration.equals("true")) + { + observation = SessionControllerGetter.getObservationBean().deleteObservationIllustration(observation); + } - // Store the new illustration (replaces former illustration if not already deleted) - if(items != null) - { - for(FileItem item:items) + // Store the new illustration (replaces former illustration if not already deleted) + if(items != null) { - if(!item.isFormField() && item.getSize() > 0) + for(FileItem item:items) { - observation = SessionControllerGetter.getObservationBean().storeObservationIllustration(observation, item); + if(!item.isFormField() && item.getSize() > 0) + { + observation = SessionControllerGetter.getObservationBean().storeObservationIllustration(observation, item); + } } } - } - // All transactions finished, we can send notifications - // if conditions are met - if(sendNotification) + // All transactions finished, we can send notifications + // if conditions are met + if(sendNotification) + { + SessionControllerGetter.getMessagingBean().sendUniversalMessage(observation); + } + + // Redirect to form + response.sendRedirect(new StringBuilder("http://") + .append(ServletUtil.getServerName(request)) + .append("/observation?action=editObservationForm&observationId=").append(observation.getObservationId()) + .append("&messageKey=").append("observationStored").toString() + + ); + } + else { - SessionControllerGetter.getMessagingBean().sendUniversalMessage(observation); + // Redirect to form with error messages + request.setAttribute("formValidation", formValidation); + request.setAttribute("observation", observation); + List<Organism> allOrganisms = em.createNamedQuery("Organism.findAll").getResultList(); + request.setAttribute("allOrganisms", allOrganisms); + // Hierarchy categories + request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); + request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList()); + request.getRequestDispatcher("/observationForm.ftl").forward(request, response); } - - // Redirect to form - response.sendRedirect(new StringBuilder("http://") - .append(ServletUtil.getServerName(request)) - .append("/observation?action=editObservationForm&observationId=").append(observation.getObservationId()) - .append("&messageKey=").append("observationStored").toString() - ); } - else + catch(NullPointerException | NumberFormatException | FormValidationException | FileUploadException ex) { - // Redirect to form with error messages - request.setAttribute("formValidation", formValidation); - request.setAttribute("observation", observation); - List<Organism> allOrganisms = em.createNamedQuery("Organism.findAll").getResultList(); - request.setAttribute("allOrganisms", allOrganisms); - // Hierarchy categories - request.setAttribute("hierarchyCategories", SessionControllerGetter.getOrganismBean().getHierarchyCategoryNames(SessionLocaleUtil.getCurrentLocale(request))); - request.setAttribute("observationMethods", em.createNamedQuery("ObservationMethod.findAll", ObservationMethod.class).getResultList()); - request.getRequestDispatcher("/observationForm.ftl").forward(request, response); + response.sendError(500, ExceptionUtil.getStackTrace(ex)); + } + catch(Exception ex) + { + ex.printStackTrace(); + response.sendError(500, ExceptionUtil.getStackTrace(ex)); } - - } - catch(NullPointerException | NumberFormatException | FormValidationException | FileUploadException ex) - { - response.sendError(500, ExceptionUtil.getStackTrace(ex)); } - catch(Exception ex) + else { - ex.printStackTrace(); - response.sendError(500, ExceptionUtil.getStackTrace(ex)); + response.sendError(403,"Access not authorized"); // HTTP Forbidden } } - else + // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER + else if(action.equals("deleteObservation")) { - response.sendError(403,"Access not authorized"); // HTTP Forbidden + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + { + try + { + Integer observationId = Integer.valueOf(request.getParameter("observationId")); + SessionControllerGetter.getObservationBean().deleteObservation(observationId); + + // Redirect to list + response.sendRedirect(new StringBuilder("http://") + .append(ServletUtil.getServerName(request)) + .append("/observation") + .append("?messageKey=").append("observationDeleted").toString() + ); + } + catch(NullPointerException | NumberFormatException ex) + { + response.sendError(500, ExceptionUtil.getStackTrace(ex)); + } + } + else + { + response.sendError(403,"Access not authorized"); // HTTP Forbidden + } } } - // Authorization: ORGANIZATION ADMIN, OBSERVER or SUPERUSER - else if(action.equals("deleteObservation")) + else if(request.getServletPath().endsWith("/map")) { - if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.OBSERVER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + // Only for superusers, organizationadmins and observation authorities + if(SessionControllerGetter.getUserBean().authorizeUser(user, + VipsLogicRole.SUPERUSER, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.OBSERVATION_AUTHORITY) + ) { try { - Integer observationId = Integer.valueOf(request.getParameter("observationId")); - SessionControllerGetter.getObservationBean().deleteObservation(observationId); - - // Redirect to list - response.sendRedirect(new StringBuilder("http://") - .append(ServletUtil.getServerName(request)) - .append("/observation") - .append("?messageKey=").append("observationDeleted").toString() + FormValidation formValidation = FormValidator.validateForm("observationMapForm", request, getServletContext()); + Organization organization = null; + if(user.isSuperUser()){ + Integer organizationId = formValidation.getFormField("organizationId") != null ? + formValidation.getFormField("organizationId").getValueAsInteger() + :user.getOrganizationId().getOrganizationId(); + organization = em.find(Organization.class, organizationId); + request.setAttribute("organizations", SessionControllerGetter.getUserBean().getOrganizations()); + request.setAttribute("organizationId", organizationId); + } + else + { + organization = user.getOrganizationId(); + request.setAttribute("organizationId", organization.getOrganizationId()); + } + + if(organization != null) + { + request.setAttribute("defaultMapCenter",organization.getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", organization.getDefaultMapZoom()); + } + + // Input control + // from: Default is start of year (jan 1st) + Date from; + Calendar cal = Calendar.getInstance(TimeZone.getDefault()); + if(!formValidation.getFormField("from").isEmpty()) + { + from = formValidation.getFormField("from").getValueAsDate(); + } + else + { + cal.setTime(SystemTime.getSystemTime()); + cal.set(cal.get(Calendar.YEAR), Calendar.JANUARY,1,0,0,0); + from = cal.getTime(); + } + request.setAttribute("from", from); + // to + Date to; + if(!formValidation.getFormField("to").isEmpty()) + { + to = formValidation.getFormField("to").getValueAsDate(); + } + else + { + cal.setTime(SystemTime.getSystemTime()); + cal.set(cal.get(Calendar.YEAR), Calendar.DECEMBER,31,23,59,59); + to = cal.getTime(); + } + request.setAttribute("to", to); + // pestId + request.setAttribute("pestId", formValidation.getFormField("pestId").isEmpty() ? null : + formValidation.getFormField("pestId").getValueAsInteger() + ); + // cropId + request.setAttribute("cropId", formValidation.getFormField("cropId").isEmpty() ? null : + formValidation.getFormField("cropId").getValueAsInteger() ); + // cropCategoryId + request.setAttribute("cropCategoryId", formValidation.getFormField("cropCategoryId").isEmpty() ? null : + formValidation.getFormField("cropCategoryId").getValueAsInteger() + ); + + request.setAttribute("messageKey", request.getParameter("messageKey")); + request.getRequestDispatcher("/observationMap.ftl").forward(request, response); } - catch(NullPointerException | NumberFormatException ex) + catch(FormValidationException ex) { response.sendError(500, ExceptionUtil.getStackTrace(ex)); } diff --git a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java index 6a89b7373a7f20ac0e3b66f6d2ea17cbfd539880..ac634ac8679b24f734219301bdb6f38e99a5862e 100644 --- a/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java +++ b/src/main/java/no/nibio/vips/logic/controller/session/ObservationBean.java @@ -22,6 +22,7 @@ package no.nibio.vips.logic.controller.session; import com.vividsolutions.jts.geom.Geometry; import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.HashSet; @@ -32,6 +33,7 @@ import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import no.nibio.vips.logic.entity.CropCategory; import no.nibio.vips.logic.entity.Gis; import no.nibio.vips.logic.entity.Message; import no.nibio.vips.logic.entity.MessageIllustration; @@ -39,6 +41,7 @@ import no.nibio.vips.logic.entity.MessageIllustrationPK; import no.nibio.vips.logic.entity.Observation; import no.nibio.vips.logic.entity.ObservationIllustration; import no.nibio.vips.logic.entity.ObservationIllustrationPK; +import no.nibio.vips.logic.entity.ObservationStatusType; import no.nibio.vips.logic.entity.Organism; import no.nibio.vips.logic.entity.Organization; import no.nibio.vips.logic.entity.PointOfInterest; @@ -153,18 +156,16 @@ public class ObservationBean { // Then persist the new ones if(observation.getGeoinfos() != null && ! observation.getGeoinfos().isEmpty()) { - for(Gis gis : observation.getGeoinfos()) - { + observation.getGeoinfos().stream().forEach((gis) -> { em.persist(gis); - } + }); Query q = em.createNativeQuery("INSERT INTO public.gis_observation(gis_id,observation_id) VALUES(:gisId,:observationId)") .setParameter("observationId", retVal.getObservationId()); - for(Gis gis:observation.getGeoinfos()) - { + observation.getGeoinfos().stream().forEach((gis) -> { q.setParameter("gisId", gis.getGisId()) .executeUpdate(); - } + }); } /* for(Geometry geom:observation.getGeometries()) @@ -299,51 +300,38 @@ public class ObservationBean { private List<Observation> getObservationsWithLocations(List<Observation> observations) { Set<Integer> locationPointOfInterestIds = new HashSet<>(); - for(Observation o:observations) - { - if(o.getLocationPointOfInterestId() != null) - { - locationPointOfInterestIds.add(o.getLocationPointOfInterestId()); - } - } + observations.stream().filter((o) -> (o.getLocationPointOfInterestId() != null)).forEach((o) -> { + locationPointOfInterestIds.add(o.getLocationPointOfInterestId()); + }); + // Nothing to do? + if(locationPointOfInterestIds.isEmpty()) + { + return observations; + } List<PointOfInterest> pois = SessionControllerGetter.getPointOfInterestBean().getPois(locationPointOfInterestIds); Map<Integer, PointOfInterest> mappedPois = new HashMap<>(); - for(PointOfInterest poi:pois) - { + pois.stream().forEach((poi) -> { mappedPois.put(poi.getPointOfInterestId(), poi); - } - for(Observation o:observations) - { - if(o.getLocationPointOfInterestId() != null) - { - o.setLocation(mappedPois.get(o.getLocationPointOfInterestId())); - } - } + }); + observations.stream().filter((o) -> (o.getLocationPointOfInterestId() != null)).forEach((o) -> { + o.setLocation(mappedPois.get(o.getLocationPointOfInterestId())); + }); return observations; } private List<Observation> getObservationsWithObservers(List<Observation> observations) { Set<Integer> userIds = new HashSet<>(); - for(Observation o:observations) - { - if(o.getUserId() != null) - { - userIds.add(o.getUserId()); - } - } + observations.stream().filter((o) -> (o.getUserId() != null)).forEach((o) -> { + userIds.add(o.getUserId()); + }); List<VipsLogicUser> users = SessionControllerGetter.getUserBean().getUsers(userIds); Map<Integer, VipsLogicUser> mappedUsers = new HashMap<>(); - for(VipsLogicUser user:users) - { + users.stream().forEach((user) -> { mappedUsers.put(user.getUserId(), user); - } - for(Observation o:observations) - { - if(o.getUserId() != null) - { - o.setUser(mappedUsers.get(o.getUserId())); - } - } + }); + observations.stream().filter((o) -> (o.getUserId() != null)).forEach((o) -> { + o.setUser(mappedUsers.get(o.getUserId())); + }); return observations; } @@ -354,5 +342,97 @@ public class ObservationBean { .getResultList(); } + public List<Observation> getFilteredObservations( + Integer organizationId, + Integer pestId, + Integer cropId, + Integer cropCategoryId, + Date from, + Date to + ) + { + // The minimum SQL + String sql = "SELECT * FROM public.observation \n" + + "WHERE status_type_id = :statusTypeId \n " + + "AND user_id IN (SELECT user_id FROM public.vips_logic_user WHERE organization_id = :organizationId) \n"; + + Map<String, Object> parameters = new HashMap<>(); + parameters.put("statusTypeId", ObservationStatusType.STATUS_APPROVED); + parameters.put("organizationId", organizationId); + + // Filter for pest + if(pestId != null && pestId > 0) + { + sql += "AND organism_id = :organismId \n"; + parameters.put("organismId", pestId); + } + // Filter either for crop or cropCategoryId + if(cropId != null && cropId > 0) + { + sql += "AND crop_organism_id = :cropOrganismId \n"; + parameters.put("cropOrganismId", cropId); + } + else if(cropCategoryId != null && cropCategoryId > 0) + { + CropCategory cropCategory = em.createNamedQuery("CropCategory.findByCropCategoryId", CropCategory.class) + .setParameter("cropCategoryId", cropCategoryId) + .getSingleResult(); + List<Integer> cropIds = Arrays.asList(cropCategory.getCropOrganismIds()); + + sql += "AND crop_organism_id IN (:cropOrganismIds) \n"; + parameters.put("cropOrganismIds", cropIds); + } + // Filter for dates + if(from != null) + { + sql += "AND time_of_observation >= :from \n"; + parameters.put("from", from); + } + if(to != null) + { + sql += "AND time_of_observation <= :to \n"; + parameters.put("to", to); + } + + Query q = em.createNativeQuery(sql, Observation.class); + // Setting the parameters one by one + parameters.keySet().stream().forEach( + (key)->q.setParameter(key, parameters.get(key)) + ); + + List<Observation> observations = q.getResultList(); + + observations.stream().forEach( + (observation)->observation.setUser(em.find(VipsLogicUser.class, observation.getUserId())) + ); + + return observations.isEmpty() ? + new ArrayList<>() + : this.getObservationsWithLocations(this.getObservationsWithGeoInfo(observations)); + } + + public List<Organism> getObservedPests(Integer organizationId) { + Query q = em.createNativeQuery("SELECT DISTINCT organism_id FROM public.observation WHERE user_id IN (" + + " SELECT user_id FROM vips_logic_user WHERE organization_id = :organizationId" + + ")"); + List<Integer> pestIds = q.setParameter("organizationId", organizationId).getResultList(); + return em.createNamedQuery("Organism.findByOrganismIds") + .setParameter("organismIds", pestIds) + .getResultList(); + + } + + public List<Organism> getObservedCrops(Integer organizationId) { + Query q = em.createNativeQuery("SELECT DISTINCT crop_organism_id FROM public.observation WHERE user_id IN (" + + " SELECT user_id FROM vips_logic_user WHERE organization_id = :organizationId" + + ")"); + List<Integer> cropIds = q.setParameter("organizationId", organizationId).getResultList(); + return em.createNamedQuery("Organism.findByOrganismIds") + .setParameter("organismIds", cropIds) + .getResultList(); + + } + + } diff --git a/src/main/java/no/nibio/vips/logic/entity/Observation.java b/src/main/java/no/nibio/vips/logic/entity/Observation.java index 2da3b8b8095062a8d195ef8ac5dd6fdb0cf80dee..24889460ff7215fcc23179aa8d65879410849b71 100644 --- a/src/main/java/no/nibio/vips/logic/entity/Observation.java +++ b/src/main/java/no/nibio/vips/logic/entity/Observation.java @@ -37,7 +37,9 @@ import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.xml.bind.annotation.XmlRootElement; import com.fasterxml.jackson.annotation.JsonIgnore; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.FetchType; @@ -173,7 +175,9 @@ public class Observation implements Serializable, no.nibio.vips.observation.Obse @Override public String getGeoinfo() { - return this.GISUtil.getGeoJSONFromGis(this.geoinfo); + Map<String, Object> properties = new HashMap<>(); + properties.put("observationId", this.getObservationId()); + return this.GISUtil.getGeoJSONFromGis(this.geoinfo, properties); } /*@Transient diff --git a/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java b/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java index afa92961efcb6a00535f3cc6f992a593c08bb4d0..1d36fe45393199ab75a9c33a08923347f6b136f1 100644 --- a/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java +++ b/src/main/java/no/nibio/vips/logic/entity/ObservationStatusType.java @@ -43,6 +43,12 @@ import javax.xml.bind.annotation.XmlRootElement; @NamedQuery(name = "ObservationStatusType.findByStatusTypeId", query = "SELECT o FROM ObservationStatusType o WHERE o.statusTypeId = :statusTypeId"), @NamedQuery(name = "ObservationStatusType.findByStatusTitle", query = "SELECT o FROM ObservationStatusType o WHERE o.statusTitle = :statusTitle")}) public class ObservationStatusType implements Serializable { + + public static final Integer STATUS_PENDING = 1; + public static final Integer STATUS_REJECTED = 2; + public static final Integer STATUS_APPROVED = 3; + + private static final long serialVersionUID = 1L; @Id diff --git a/src/main/java/no/nibio/vips/logic/entity/PointOfInterest.java b/src/main/java/no/nibio/vips/logic/entity/PointOfInterest.java index bc73ba0467aa1d2daa8a7f0c4da7a202c8a9f2b4..a4b2784b47ffc2d357e0416031561bb39b0a023f 100644 --- a/src/main/java/no/nibio/vips/logic/entity/PointOfInterest.java +++ b/src/main/java/no/nibio/vips/logic/entity/PointOfInterest.java @@ -324,7 +324,9 @@ public class PointOfInterest implements Serializable, Comparable { @Transient public String getGeoJSON() { - return this.gisUtil.getGeoJSONFromGeometry(this.getGisGeom()); + Map<String, Object> properties = new HashMap<>(); + properties.put("pointOfInterestId", this.getPointOfInterestId()); + return this.gisUtil.getGeoJSONFromGeometry(this.getGisGeom(), properties); } /** diff --git a/src/main/java/no/nibio/vips/logic/service/LogicService.java b/src/main/java/no/nibio/vips/logic/service/LogicService.java index 2f83d85cd3d9bb59f27b26a7fc09df026bb8237e..6178b04672741a8caba1704191e894f8cf830d5f 100644 --- a/src/main/java/no/nibio/vips/logic/service/LogicService.java +++ b/src/main/java/no/nibio/vips/logic/service/LogicService.java @@ -635,36 +635,8 @@ public class LogicService { return Response.ok().entity(observations).build(); } - @GET - @Path("observation/{observationId}") - @Produces("application/json;charset=UTF-8") - public Response getObservation(@PathParam("observationId") Integer observationId){ - return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservation(observationId)).build(); - } - /** - * Publicly available observations per organization - * @param organizationId - * @return APPROVED observations - */ - @GET - @Path("observation/list/{organizationId}") - @Produces("application/json;charset=UTF-8") - public Response getObservations(@PathParam("organizationId") Integer organizationId){ - return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservations(organizationId, Observation.STATUS_TYPE_ID_APPROVED)).build(); - } - /** - * Publicly available observations per organization - * @param organizationId - * @return APPROVED observations - */ - @GET - @Path("observation/broadcast/list/{organizationId}") - @Produces("application/json;charset=UTF-8") - public Response getBroadcastObservations(@PathParam("organizationId") Integer organizationId){ - return Response.ok().entity(SessionControllerGetter.getObservationBean().getBroadcastObservations(organizationId)).build(); - } /** * Service available locally for cron jobs. Most useful on test servers diff --git a/src/main/java/no/nibio/vips/logic/service/ObservationService.java b/src/main/java/no/nibio/vips/logic/service/ObservationService.java new file mode 100644 index 0000000000000000000000000000000000000000..a59bc42db8b675a77613a8823bccd8880b02bc4a --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/service/ObservationService.java @@ -0,0 +1,155 @@ +/* + * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.service; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; +import no.nibio.vips.logic.entity.Observation; +import no.nibio.vips.logic.util.Globals; +import no.nibio.vips.logic.util.SessionControllerGetter; + +/** + * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +@Path("rest/observation") +public class ObservationService { + + /** + * NOTE TO SELF + * How to query for observations within a bounding box + * Select * from gis where ST_Intersects( + * ST_SetSRID(ST_MakeBox2D(ST_MakePoint(2.9004, 57.7511), ST_MakePoint(32.4316, 71.3851)),4326), + * gis_geom); + * First point is SW, last is NE (but could be anything?) + * @param organizationId + * @param pestId + * @param cropId + * @param cropCategoryId + * @param from + * @param to + * @return + */ + @GET + @Path("filter/{organizationId}") + @Produces("application/json;charset=UTF-8") + public Response getFilteredObservations( + @PathParam("organizationId") Integer organizationId, + @QueryParam("pestId") Integer pestId, + @QueryParam("cropId") Integer cropId, + @QueryParam("cropCategoryId") Integer cropCategoryId, + @QueryParam("from") String fromStr, + @QueryParam("to") String toStr + + ) + { + SimpleDateFormat format = new SimpleDateFormat(Globals.defaultDateFormat); + //TODO Set correct timeZone!!! + Date from = null; + Date to = null; + try + { + from = fromStr != null ? format.parse(fromStr) : null; + to = toStr != null ? format.parse(toStr) : null; + } + catch(ParseException ex){ System.out.println("ERROR");} + + List<Observation> filteredObservations = SessionControllerGetter.getObservationBean().getFilteredObservations( + organizationId, + pestId, + cropId, + cropCategoryId, + from, + to + ); + return Response.ok().entity(filteredObservations).build(); + } + + /** + * Get a list of all observed pests for one organization + * Practical for building effective select lists + * TODO: Should be cached?? + * @param organizationId + * @return + */ + @GET + @Path("pest/{organizationId}") + @Produces("application/json;charset=UTF-8") + public Response getObservedPests(@PathParam("organizationId") Integer organizationId) + { + return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservedPests(organizationId)).build(); + } + + /** + * Get a list of all crop cultures where observations have been made for one organization + * Practical for building effective select lists + * TODO: Should be cached?? + * @param organizationId + * @return + */ + @GET + @Path("crop/{organizationId}") + @Produces("application/json;charset=UTF-8") + public Response getObservedCrops(@PathParam("organizationId") Integer organizationId) + { + return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservedCrops(organizationId)).build(); + } + + + /** + * Publicly available observations per organization + * @param organizationId + * @return APPROVED observations + */ + @GET + @Path("list/{organizationId}") + @Produces("application/json;charset=UTF-8") + public Response getObservations(@PathParam("organizationId") Integer organizationId){ + return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservations(organizationId, Observation.STATUS_TYPE_ID_APPROVED)).build(); + } + + /** + * Publicly available observations per organization + * @param organizationId + * @return APPROVED observations + */ + @GET + @Path("broadcast/list/{organizationId}") + @Produces("application/json;charset=UTF-8") + public Response getBroadcastObservations(@PathParam("organizationId") Integer organizationId){ + return Response.ok().entity(SessionControllerGetter.getObservationBean().getBroadcastObservations(organizationId)).build(); + } + + @GET + @Path("{observationId}") + @Produces("application/json;charset=UTF-8") + public Response getObservation(@PathParam("observationId") Integer observationId){ + return Response.ok().entity(SessionControllerGetter.getObservationBean().getObservation(observationId)).build(); + } + +} diff --git a/src/main/java/no/nibio/vips/logic/util/GISUtil.java b/src/main/java/no/nibio/vips/logic/util/GISUtil.java index 0e7fa7ab2497451bfce8663625d288bb17525725..a32327d722d508160a5b43f9fb1f61f6372a9d38 100644 --- a/src/main/java/no/nibio/vips/logic/util/GISUtil.java +++ b/src/main/java/no/nibio/vips/logic/util/GISUtil.java @@ -25,8 +25,6 @@ import com.vividsolutions.jts.geom.GeometryFactory; import com.vividsolutions.jts.geom.Point; import com.vividsolutions.jts.geom.PrecisionModel; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; import java.util.Map; import no.nibio.vips.logic.entity.Gis; @@ -82,14 +80,13 @@ public class GISUtil { return retVal; } - public String getGeoJSONFromGis(List<Gis> geoinfo) + public String getGeoJSONFromGis(List<Gis> geoinfo, Map<String, Object> properties) { if(geoinfo == null || geoinfo.isEmpty()) { return ""; } List<Feature> features = new ArrayList<>(); - Map<String, Object> properties = new HashMap<>(); GeoJSONWriter writer = new GeoJSONWriter(); geoinfo.stream().forEach( (gis)->features.add(new Feature(writer.write(gis.getGisGeom()), properties)) @@ -99,28 +96,26 @@ public class GISUtil { return json.toString(); } - public String getGeoJSONFromGeometries(List<Geometry> geometries) + public String getGeoJSONFromGeometries(List<Geometry> geometries, Map<String, Object> properties) { if(geometries == null || geometries.isEmpty()) { return ""; } List<Feature> features = new ArrayList<>(); - Map<String, Object> properties = new HashMap<>(); GeoJSONWriter writer = new GeoJSONWriter(); geometries.stream().forEach((geometry)->features.add(new Feature(writer.write(geometry), properties))); FeatureCollection json = writer.write(features); return json.toString(); } - public String getGeoJSONFromGeometry(Geometry geometry) + public String getGeoJSONFromGeometry(Geometry geometry,Map<String, Object> properties) { if(geometry == null) { return ""; } List<Feature> features = new ArrayList<>(); - Map<String, Object> properties = new HashMap<>(); GeoJSONWriter writer = new GeoJSONWriter(); features.add(new Feature(writer.write(geometry), properties)); diff --git a/src/main/java/no/nibio/vips/logic/web/js/JSEnvironment.java b/src/main/java/no/nibio/vips/logic/web/js/JSEnvironment.java new file mode 100644 index 0000000000000000000000000000000000000000..2db214be6575e06479be9164c85d70315cd17518 --- /dev/null +++ b/src/main/java/no/nibio/vips/logic/web/js/JSEnvironment.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +package no.nibio.vips.logic.web.js; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import no.nibio.vips.logic.i18n.SessionLocaleUtil; + +/** + * Provides JavaScript files with environment information + * @copyright 2016 <a href="http://www.nibio.no/">NIBIO</a> + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +public class JSEnvironment extends HttpServlet { + + /** + * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods. + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + protected void processRequest(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.setContentType("application/javascript;charset=UTF-8"); + try (PrintWriter out = response.getWriter()) { + /* TODO output your page here. You may use following sample code. */ + out.println("var environment = {"); + out.println(" currentLanguage: \"" + SessionLocaleUtil.getCurrentLocale(request) + "\","); + out.println(" defaultLanguage: \"en\""); + out.println("};"); + } + } + + // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code."> + /** + * Handles the HTTP <code>GET</code> method. + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + /** + * Handles the HTTP <code>POST</code> method. + * @param request servlet request + * @param response servlet response + * @throws ServletException if a servlet-specific error occurs + * @throws IOException if an I/O error occurs + */ + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + processRequest(request, response); + } + + /** + * Returns a short description of the servlet. + * @return a String containing servlet description + */ + @Override + public String getServletInfo() { + return "Short description"; + }// </editor-fold> + +} diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties index 319c9a0dc9d04bbeea90775c762a3aedb86d67d5..f1e8e3a3cda982a2de6c60911ee3b433c7a07047 100644 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts.properties @@ -428,3 +428,4 @@ uppercase=Upper case allSystems=All systems cropCategoriesFor=Crop categories for cropCategoryUpdated=Crop category was updated +observationMap=Observation map diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties index d88dd091035e7a6408cb505eaf8b773eaaad9d3a..7e128fde97fa9d7f3e93809a175147b9ea0f12f2 100644 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_bs.properties @@ -428,3 +428,4 @@ uppercase=Upper case allSystems=All systems cropCategoriesFor=Crop categories for cropCategoryUpdated=Crop category was updated +observationMap=Observation map diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties index bcdf18e97b76948fdc4ab87608f445e6b5ae3baa..431ac9c44ea84c07d57891b1828412d44a63fca2 100644 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_hr.properties @@ -427,3 +427,4 @@ uppercase=Upper case allSystems=All systems cropCategoriesFor=Crop categories for cropCategoryUpdated=Crop category was updated +observationMap=Observation map diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties index 305abc4f611bbb1190e449b59084aecff27d1994..671364832bc45c0f0258fd66e184f3e86c2a4b6c 100644 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_nb.properties @@ -428,3 +428,4 @@ uppercase=STOR bokstav allSystems=Alle system cropCategoriesFor=Kulturkategorier for cropCategoryUpdated=Kulturkategorien ble oppdatert +observationMap=Observasjonskart diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties index dacbfef7cc8e04b6ae2ba30282ef7979af8d2093..620d6b1577b96cf9accadf3e63e29463b6b12ed5 100644 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_sr.properties @@ -428,3 +428,4 @@ uppercase=Upper case allSystems=All systems cropCategoriesFor=Crop categories for cropCategoryUpdated=Crop category was updated +observationMap=Observation map diff --git a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties index 319c9a0dc9d04bbeea90775c762a3aedb86d67d5..f1e8e3a3cda982a2de6c60911ee3b433c7a07047 100644 --- a/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties +++ b/src/main/resources/no/nibio/vips/logic/i18n/vipslogictexts_zh_CN.properties @@ -428,3 +428,4 @@ uppercase=Upper case allSystems=All systems cropCategoriesFor=Crop categories for cropCategoryUpdated=Crop category was updated +observationMap=Observation map diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index a4e31cf71311cf06637080417e944cd3bba7e0a7..d84c500de00bd07f911345f402eccc1c1df5c72b 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -79,6 +79,10 @@ <servlet-name>CropCategoryController</servlet-name> <servlet-class>no.nibio.vips.logic.controller.servlet.CropCategoryController</servlet-class> </servlet> + <servlet> + <servlet-name>JSEnvironment</servlet-name> + <servlet-class>no.nibio.vips.logic.web.js.JSEnvironment</servlet-class> + </servlet> <servlet-mapping> <servlet-name>PointOfInterestController</servlet-name> <url-pattern>/poi/*</url-pattern> @@ -126,6 +130,7 @@ <servlet-mapping> <servlet-name>ObservationServlet</servlet-name> <url-pattern>/observation</url-pattern> + <url-pattern>/observation/map</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>NotificationSubscriptionController</servlet-name> @@ -143,6 +148,10 @@ <servlet-name>CropCategoryController</servlet-name> <url-pattern>/organism/cropcategory</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>JSEnvironment</servlet-name> + <url-pattern>/js/environment.js</url-pattern> + </servlet-mapping> <welcome-file-list> <welcome-file>index.html</welcome-file> </welcome-file-list> @@ -280,4 +289,4 @@ <error-code>404</error-code> <location>/error/404</location> </error-page> -</web-app> +</web-app> \ No newline at end of file diff --git a/src/main/webapp/css/vipslogic.css b/src/main/webapp/css/vipslogic.css index 2de1c3e04487f57adb3878e627eb429e84159c57..533bbcc693524b5906fd0e1c31fdde00df3bc543 100644 --- a/src/main/webapp/css/vipslogic.css +++ b/src/main/webapp/css/vipslogic.css @@ -263,4 +263,9 @@ legend { .referenceTranslationText { font-size: smaller; font-style: italic; +} + +div.popover { + min-width: 350px; + max-width: 400px !important; } \ No newline at end of file diff --git a/src/main/webapp/formdefinitions/observationMapForm.json b/src/main/webapp/formdefinitions/observationMapForm.json new file mode 100644 index 0000000000000000000000000000000000000000..77149662796904cf768c17df4098981180afe0c3 --- /dev/null +++ b/src/main/webapp/formdefinitions/observationMapForm.json @@ -0,0 +1,58 @@ +{ + "_licenseNote": [ + "Copyright (c) 2016 NIBIO <http://www.nibio.no/>. ", + "", + "This file is part of VIPSLogic. ", + "VIPSLogic is free software: you can redistribute it and/or modify ", + "it under the terms of the NIBIO Open Source License as published by ", + "NIBIO, either version 1 of the License, or (at your option) any ", + "later version. ", + "", + "VIPSLogic is distributed in the hope that it will be useful, ", + "but WITHOUT ANY WARRANTY; without even the implied warranty of ", + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ", + "NIBIO Open Source License for more details. ", + "", + "You should have received a copy of the NIBIO Open Source License ", + "along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. " + ], + "_comment" : "Simplify input check for observation map", + "fields": [ + { + "name" : "organizationdId", + "dataType" : "INTEGER", + "required" : false + }, + { + "name" : "pestId", + "dataType" : "INTEGER", + "fieldType" : "SELECT_SINGLE", + "required" : false, + "nullValue" : "-1" + }, + { + "name" : "cropId", + "dataType" : "INTEGER", + "fieldType" : "SELECT_SINGLE", + "required" : false, + "nullValue" : "-1" + }, + { + "name" : "cropCategoryId", + "dataType" : "INTEGER", + "fieldType" : "SELECT_SINGLE", + "required" : false, + "nullValue" : "-1" + }, + { + "name" : "from", + "dataType" : "DATE", + "required" : false + }, + { + "name" : "to", + "dataType" : "DATE", + "required" : false + } + ] +} diff --git a/src/main/webapp/js/observationMap.js b/src/main/webapp/js/observationMap.js new file mode 100644 index 0000000000000000000000000000000000000000..5d5955ef52fe41c88f11eb17fd3916fe4a71b724 --- /dev/null +++ b/src/main/webapp/js/observationMap.js @@ -0,0 +1,309 @@ +/* + * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the NIBIO Open Source License as published by + * NIBIO, either version 1 of the License, or (at your option) any + * later version. + * + * VIPSLogic is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * NIBIO Open Source License for more details. + * + * You should have received a copy of the NIBIO Open Source License + * along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. + * + */ + +var allObservations = []; // Populated asynchronously +var drawnFeatures = []; // Populated asynchronously + +/* + * Observation map + * @author Tor-Einar Skog <tor-einar.skog@nibio.no> + */ +var initMap = function( + center, + zoomLevel, + organizationId, + from, + to, + pestId, + cropId, + cropCategoryId + ) +{ + // Background layer is OpenStreetMap + var backgroundLayer = new ol.layer.Tile({ + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: mapConstants.MAP_ATTRIBUTION + }) + ] + }) + }); + + // Layer for popup + var popOverlay = new ol.Overlay({ + element: document.getElementById("popover") + }); + + // Creating the map + var map = new ol.Map({ + target: 'observationMap', + layers: [backgroundLayer], + overlays: [popOverlay], + renderer: 'canvas' + }); + + var centerPosition = ol.proj.transform(center, 'EPSG:4326', map.getView().getProjection().getCode()); + + // Setting zoom and center for the map (need to do this after creating map. so that we kan transform our + // center to correct map projection) + var view = new ol.View({ + center: centerPosition, + zoom:zoomLevel + }); + map.setView(view); + + // Need to build the query string + var params = []; + + if(from !== "") + { + params.push("from=" + from); + } + if(to !== "") + { + params.push("to=" + to); + } + if(pestId !== null) + { + params.push("pestId=" + pestId); + } + if(cropId !== null) + { + params.push("cropId=" + cropId); + } + if(cropCategoryId !== null) + { + params.push("cropCategoryId=" + cropCategoryId); + } + + + $.getJSON( "/rest/observation/filter/" + organizationId + (params.length > 0 ? "?" + params.join("&") : ""), function( data ) { + allObservations = data; + renderObservationTable(data); + var geoJSON = {"type":"FeatureCollection","features":[]}; + for(var i=0;i<data.length;i++) + { + var observation = data[i]; + var obsFeatures = null; + if(observation.location !== null && observation.location.geoJSON !== null) + { + + obsFeatures = JSON.parse(observation.location.geoJSON).features; + } + else if(observation.geoinfo !== null) + { + obsFeatures = JSON.parse(observation.geoinfo).features; + } + + for(var j=0; j<obsFeatures.length; j++) + { + geoJSON.features.push(obsFeatures[j]); + } + } + var features = new ol.Collection(); + var featureOverlay = new ol.layer.Vector({ + source: new ol.source.Vector({ + features: features + }), + style: new ol.style.Style({ + fill: new ol.style.Fill({ + color: 'rgba(255, 0, 255, 0.2)' + }), + stroke: new ol.style.Stroke({ + color: '#ff00ff', + width: 2 + }), + image: new ol.style.Circle({ + radius: 7, + fill: new ol.style.Fill({ + color: '#ff00ff' + }) + }) + }) + }); + + + var format = new ol.format.GeoJSON(); + drawnfeatures = format.readFeatures(geoJSON, { + dataProjection: 'EPSG:4326', + featureProjection: map.getView().getProjection().getCode() + }); + + featureOverlay.getSource().addFeatures(drawnfeatures); + //console.log(featureOverlay); + featureOverlay.setMap(map); + if(drawnfeatures.length > 0) + { + extent = featureOverlay.getSource().getExtent(); + map.getView().fit(extent, map.getSize()); + } + }); + + // Using Bootstrap's popover plugin. See http://getbootstrap.com/javascript/#popovers + var poiDetails = $("#popover"); + + var displayFeatureDetails = function(pixel, coordinate) { + var feature = map.forEachFeatureAtPixel(pixel, function(feature,layer){ + return feature; + }); + + if (feature) { + + // Position the popup, and hiding it + // Resetting information from (possible) former popups + var geometry = feature.getGeometry(); + popOverlay.setPosition(ol.extent.getCenter(geometry.getExtent())); + // Get the observation that this feature belongs to + var observation = getObservation(feature.get("observationId")); + //popOverlay.setPosition(geometry.getCoordinates()); + poiDetails.popover('destroy'); + var illustrationElm = ""; + if(observation.observationIllustrationSet.length == 1) + { + var illustration = observation.observationIllustrationSet[0]; + illustrationElm = "<img src='/static/images/observations/" + observation.organismId + "/" + illustration.observationIllustrationPK.fileName + "' class='img-responsive'/>"; + } + // Create the popup, showing it + poiDetails.popover({ + animation: true, + trigger: 'manual', + html: true, + placement: "auto top", + title: "<a href='/observation?action=editObservationForm&observationId=" + observation.observationId + "' target='new'>" + observation.observationHeading + "</a>", + content: "[" + moment(observation.timeOfObservation).format("YYYY-MM-DD HH:mm ZZ") + "]: " + + observation.observationText + + illustrationElm + }); + + + poiDetails.popover('show'); + + + } else { + poiDetails.popover('destroy'); + } + }; + + map.on('singleclick', function(evt) { + var pixel = map.getEventPixel(evt.originalEvent); + displayFeatureDetails(pixel); + }); + + +} + +var getObservation = function(observationId) +{ + for(var i=0; i<allObservations.length;i++) + { + if(allObservations[i].observationId == observationId) + { + return allObservations[i]; + } + } + return null; +} + + +var renderObservationTable = function(data) +{ + var tbody = document.getElementById("observationTableBody"); + var tbodyHTML = []; + for(var i=0; i<data.length;i++) + { + var obs = data[i]; + tbodyHTML.push("<tr>"); + tbodyHTML.push("<td>" + moment(obs.timeOfObservation).format("YYYY-MM-DD HH:mm ZZ") + "</td>"); + tbodyHTML.push("<td>" + getLocalizedOrganismName(obs.organism) + "</td>"); + tbodyHTML.push("<td>" + getLocalizedOrganismName(obs.cropOrganism) + "</td>"); + tbodyHTML.push("<td>" + (obs.location != null ? obs.location.name : "") + "</td>"); + tbodyHTML.push("<td>" + obs.user.firstName + " " + obs.user.lastName + "</td>"); + tbodyHTML.push("<td>" + obs.observationHeading + "</td>"); + tbodyHTML.push("<td></td>"); + tbodyHTML.push("</tr>"); + } + tbody.innerHTML = tbodyHTML.join("\n"); +} + +// TODO: Set selected if possible +var renderOrganismField = function(organismList, fieldId, selectedId) +{ + // Sort alphabetically by local name + organismList.sort(function(a,b){ + if (getLocalizedOrganismName(a) < getLocalizedOrganismName(b)) return -1; + if (getLocalizedOrganismName(a) > getLocalizedOrganismName(b)) return 1; + return 0; + }); + var list = document.getElementById(fieldId); + list.options.length=0; + list.options[0] = new Option("",""); // For the chosenjs to print data-placeholder + for(var i=0;i<organismList.length;i++) + { + var organism = organismList[i]; + var newOption = new Option(getLocalizedOrganismName(organism),organism.organismId); + if(organism.organismId === selectedId) + { + newOption.selected = true; + } + list.options[list.options.length] = newOption; + } +} + +var renderCropCategoryField = function(cropCategoryList, selectedId) +{ + // TODO: Sort by local name + cropCategoryList.sort(function(a,b){ + if (getLocalizedCropCategoryName(a) < getLocalizedCropCategoryName(b)) return -1; + if (getLocalizedCropCategoryName(a) > getLocalizedCropCategoryName(b)) return 1; + return 0; + }); + var list = document.getElementById("cropCategoryList"); + list.options.length=0; + list.options[0] = new Option("",""); // For the chosenjs to print data-placeholder + for(var i=0;i<cropCategoryList.length;i++) + { + var cropCategory = cropCategoryList[i]; + var newOption = new Option(getLocalizedCropCategoryName(cropCategory),cropCategory.cropCategoryId); + if(cropCategory.cropCategoryId === selectedId) + { + newOption.selected = true; + } + list.options[list.options.length] = newOption; + } +} + +var initForm = function(organizationId, + pestId, + cropId, + cropCategoryId, + postRenderFormActions + ) +{ + $.getJSON( "/rest/observation/pest/" + organizationId , function( pestList ) { + renderOrganismField(pestList, "pestList", pestId); + $.getJSON( "/rest/observation/crop/" + organizationId , function( cropList ) { + renderOrganismField(cropList, "cropList", cropId); + $.getJSON( "/rest/organism/cropcategory/" + organizationId , function( cropCategoryList ) { + renderCropCategoryField(cropCategoryList, cropCategoryId); + postRenderFormActions(); // Activate chosen.js + }); + }); + }); +} diff --git a/src/main/webapp/js/util.js b/src/main/webapp/js/util.js index adebd204fecb46cff0e7516df55a3bc4c5e2e461..92fa45b1db14cfe436f02fe39bacd6afc2b4fc5c 100644 --- a/src/main/webapp/js/util.js +++ b/src/main/webapp/js/util.js @@ -34,3 +34,81 @@ var compareSelectListOptions = function(a,b) } return 0; } + +/** + * Depends on the value of currentLanguage and defaultLanguage in /currentLanguage.js + * @param organism + * @returns {String} + */ +function getLocalizedOrganismName(organism) +{ + // Fallback in case nothing works + if(organism === null) + { + return gettext("Unnamed"); + } + // Attempting the following languages (in order): current language, default language, English + var languages = [environment.currentLanguage, environment.defaultLanguage, "en"]; + for(var j in languages) + { + for(var i in organism.organismLocaleSet) + { + var localeSet = organism.organismLocaleSet[i]; + //console.log(localeSet); + if(localeSet.organismLocalePK.locale == languages[j]) + { + return localeSet.localName; + } + } + } + // Then we try the latin name + if(organism.latinName !== null + && organism.latinName !== "") + { + return organism.latinName; + } + // Then the trade name + if(organism.tradeName !== null + && organism.tradeName !== "") + { + return organism.tradeName; + } + // Then we give up + return gettext("Unnamed"); +} + +/** + * Depends on the value of currentLanguage and defaultLanguage in /currentLanguage.js + * @param cropCategory + * @returns {String} + */ +function getLocalizedCropCategoryName(cropCategory) +{ + // Fallback in case nothing works + if(cropCategory === null) + { + return "Unnamed"; + } + // Attempting the following languages (in order): current language, default language, English + var languages = [environment.currentLanguage, environment.defaultLanguage, "en"]; + for(var j in languages) + { + for(var i in cropCategory.cropCategoryLocalSet) + { + var localeSet = cropCategory.cropCategoryLocalSet[i]; + if(localeSet.cropCategoryLocalPK.locale.trim() == languages[j].trim()) + { + return localeSet.localName; + } + } + } + // Then we try the latin name + if(cropCategory.defaultName !== null + && cropCategory.defaultName !== "") + { + return cropCategory.defaultName; + } + // Then we give up + return "Unnamed"; +} + diff --git a/src/main/webapp/js/weatherStationListMap.js b/src/main/webapp/js/weatherStationListMap.js index b83affc1b9f8ba77e28076739eac993f8ac6dfc1..7e496be845b246d101036ba649072bda0920943c 100644 --- a/src/main/webapp/js/weatherStationListMap.js +++ b/src/main/webapp/js/weatherStationListMap.js @@ -93,7 +93,7 @@ function initMap(center, zoomLevel, organizationId) popOverlay.setPosition(geometry.getCoordinates()); poiDetails.popover('destroy'); // Create the popup, showing it - poiDetails.popover({ + poiDetails.popover({ animation: true, trigger: 'manual', html: true, diff --git a/src/main/webapp/templates/observationMap.ftl b/src/main/webapp/templates/observationMap.ftl new file mode 100644 index 0000000000000000000000000000000000000000..ee8f322543d9c4a643dc86b4036b9ec46d2113f1 --- /dev/null +++ b/src/main/webapp/templates/observationMap.ftl @@ -0,0 +1,110 @@ +<#-- + Copyright (c) 2016 NIBIO <http://www.nibio.no/>. + + This file is part of VIPSLogic. + VIPSLogic is free software: you can redistribute it and/or modify + it under the terms of the NIBIO Open Source License as published by + NIBIO, either version 1 of the License, or (at your option) any + later version. + + VIPSLogic is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + NIBIO Open Source License for more details. + + You should have received a copy of the NIBIO Open Source License + along with VIPSLogic. If not, see <http://www.nibio.no/licenses/>. +--><#include "master.ftl"> +<#setting time_zone=user.organizationId.defaultTimeZone!"UTC"> +<#macro page_head> + <title>${i18nBundle.observationMap}</title> +</#macro> +<#macro custom_css> + <link rel="stylesheet" type="text/css" href="/css/3rdparty/ol.css"/> + <link rel="stylesheet" type="text/css" href="/css/3rdparty/chosen.min.css"/> +</#macro> +<#macro custom_js> + <script type="text/javascript" src="/js/3rdparty/ol.js"></script> + <script type="text/javascript" src="/js/observationMap.js"></script> + <script type="text/javascript" src="/js/3rdparty/moment.min.js"></script> + <script type="text/javascript" src="/js/3rdparty/chosen.jquery.min.js"></script> + <script type="text/javascript" src="/js/constants.js"></script> + <script type="text/javascript" src="/js/util.js"></script> + <script type="text/javascript" src="/js/environment.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + initMap( + [${(defaultMapCenter.x?c)!"0"}, ${(defaultMapCenter.y?c)!"0"}], + ${defaultMapZoom!"1"}, + ${organizationId!user.organizationId.organizationId}, + "${from?date}", + "${to?date}", + ${pestId!"null"}, + ${cropId!"null"}, + ${cropCategoryId!"null"} + ); + + + + initForm(${organizationId!user.organizationId.organizationId}, + ${pestId!"null"}, + ${cropId!"null"}, + ${cropCategoryId!"null"}, + function() {$(".chosen-select").chosen({allow_single_deselect: true});} // Must do this after select lists have been populated + ); + + + + }); + </script> +</#macro> +<#macro page_contents> +<div class="singleBlockContainer"> + <h1>${i18nBundle.observationMap}</h1> + + <#if messageKey?has_content> + <div class="alert alert-success">${i18nBundle(messageKey)}</div> + </#if> + <div id="observationMap" class="map"> + <div id="popover"></div> + </div> + <form class="form-inline" method="get" action="/observation/map"> + <div class="form-group"> + <input class="form-control" type="date" name="from" value="${from?date}"/> + </div> + - + <div class="form-group"> + <input class="form-control" type="date" name="to" value="${to?date}"/> + </div> + <div class="form-group"> + <select name="pestId" id="pestList" class="form-control chosen-select" data-placeholder="${i18nBundle.pestOrganismId}"> + </select> + </div> + <div class="form-group"> + <select name="cropId" id="cropList" class="form-control chosen-select" data-placeholder="${i18nBundle.cropOrganismId}"> + </select> + </div> + <div class="form-group"> + <select name="cropCategoryId" id="cropCategoryList" class="form-control chosen-select" data-placeholder="${i18nBundle.cropCategoryIds}"> + </select> + </div> + <button type="submit" class="btn btn-default">${i18nBundle.submit}</button> + </form> + <div class="table-responsive"> + <table class="table table-striped" id="observationTable"> + <thead> + <th>${i18nBundle.timeOfObservation}</th> + <th>${i18nBundle.organism}</th> + <th>${i18nBundle.cropOrganismId}</th> + <th>${i18nBundle.location}</th> + <th>${i18nBundle.observer}</th> + <th>${i18nBundle.heading}</th> + <th></th> + </thead> + <tbody id="observationTableBody"> + </tbody> + </table> + </div> +</div> +</#macro> +<@page_html/> diff --git a/src/main/webapp/templates/weatherstationList.ftl b/src/main/webapp/templates/weatherstationList.ftl index c2900b81a3049a1dca51c3900f58523f0ae867e4..bef608bc80c5c2a41346c59068022412a7f60edc 100644 --- a/src/main/webapp/templates/weatherstationList.ftl +++ b/src/main/webapp/templates/weatherstationList.ftl @@ -19,7 +19,7 @@ <title>${i18nBundle.weatherStations}</title> </#macro> <#macro custom_css> - <link rel="stylesheet" type="text/css" href="/css/3rdparty/ol.css"/ > + <link rel="stylesheet" type="text/css" href="/css/3rdparty/ol.css"/> </#macro> <#macro custom_js> <script type="text/javascript" src="/js/3rdparty/ol.js"></script> diff --git a/src/test/java/no/nibio/vips/logic/util/GISUtilTest.java b/src/test/java/no/nibio/vips/logic/util/GISUtilTest.java index 9bd5c491fe948b90b23b88c285368bc96755512a..c36f8f6e5cfb1fbc77ce6ac9a22b318b9549aa3b 100644 --- a/src/test/java/no/nibio/vips/logic/util/GISUtilTest.java +++ b/src/test/java/no/nibio/vips/logic/util/GISUtilTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 NIBIO <http://www.nibio.no/>. + * Copyright (c) 2016 NIBIO <http://www.nibio.no/>. * * This file is part of VIPSLogic. * VIPSLogic is free software: you can redistribute it and/or modify @@ -18,14 +18,18 @@ */ package no.nibio.vips.logic.util; -import com.vividsolutions.jts.geom.Geometry; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import static org.junit.Assert.*; +import org.wololo.geojson.Feature; +import org.wololo.geojson.Geometry; +import org.wololo.geojson.Point; /** * @@ -58,11 +62,14 @@ public class GISUtilTest { @Test public void testRoundtrip() { System.out.println("testRoundtrip"); - String json = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[10.669097900390623,59.753628151319106],[10.612792968749998,59.70309199431276],[10.726776123046873,59.705863076677105],[10.669097900390623,59.753628151319106]]]},\"properties\":{}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[10.704803466796873,59.64831609639066]},\"properties\":{}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[10.649871826171871,59.67051458978321],[10.791320800781248,59.67328836837126]]},\"properties\":{}}]}"; + String json = "{\"type\":\"FeatureCollection\",\"features\":[{\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[10.669097900390623,59.753628151319106],[10.612792968749998,59.70309199431276],[10.726776123046873,59.705863076677105],[10.669097900390623,59.753628151319106]]]},\"properties\":{\"observationId\":12}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"Point\",\"coordinates\":[10.704803466796873,59.64831609639066]},\"properties\":{\"observationId\":12}},{\"type\":\"Feature\",\"geometry\":{\"type\":\"LineString\",\"coordinates\":[[10.649871826171871,59.67051458978321],[10.791320800781248,59.67328836837126]]},\"properties\":{\"observationId\":12}}]}"; + System.out.println("JSON=" + json); GISUtil instance = new GISUtil(); //List<Geometry> expResult = null; - List<Geometry> geometries = instance.getGeometriesFromGeoJSON(json); - String result = instance.getGeoJSONFromGeometries(geometries); + List<com.vividsolutions.jts.geom.Geometry> geometries = instance.getGeometriesFromGeoJSON(json); + Map<String, Object> properties = new HashMap<>(); + properties.put("observationId", 12); + String result = instance.getGeoJSONFromGeometries(geometries, properties); //System.out.println(result); assertEquals(json, result); } diff --git a/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java b/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java index 591d2488fcae9a8566b93619819651afcc198366..f563aa3da7ca121eda47fd73707ee65150d236c2 100644 --- a/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java +++ b/src/test/java/no/nibio/vips/util/weather/YrWeatherForecastProviderTest.java @@ -18,12 +18,15 @@ */ package no.nibio.vips.util.weather; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.Point; import no.nibio.vips.util.weather.YrWeatherForecastProvider; import no.nibio.vips.util.weather.ParseWeatherDataException; import java.util.Collections; import java.util.List; import no.nibio.vips.entity.WeatherObservation; import no.nibio.vips.logic.entity.PointOfInterestWeatherStation; +import no.nibio.vips.logic.util.GISUtil; import org.junit.After; import org.junit.AfterClass; import org.junit.Before; @@ -64,9 +67,12 @@ public class YrWeatherForecastProviderTest { System.out.println("getWeatherForecasts"); PointOfInterestWeatherStation weatherStation = new PointOfInterestWeatherStation(); //Testing in Bosnia (Rodoc, Mostar) - weatherStation.setAltitude(67.0); + GISUtil gisUtil = new GISUtil(); + Point p = gisUtil.createPointWGS84(new Coordinate(17.8094, 43.301, 67.0)); + /*weatherStation.setAltitude(67.0); weatherStation.setLongitude(17.8094); - weatherStation.setLatitude(43.301); + weatherStation.setLatitude(43.301);*/ + weatherStation.setGisGeom(p); YrWeatherForecastProvider instance = new YrWeatherForecastProvider(); try {