diff --git a/src/main/java/no/bioforsk/vips/logic/controller/servlet/ObservationController.java b/src/main/java/no/bioforsk/vips/logic/controller/servlet/ObservationController.java index e9071ecd2c8e3a9053de6552a5dbb5872f8a7454..2bc1d865518c6e07d7ee2bd5a8c5e192b093d97f 100644 --- a/src/main/java/no/bioforsk/vips/logic/controller/servlet/ObservationController.java +++ b/src/main/java/no/bioforsk/vips/logic/controller/servlet/ObservationController.java @@ -80,6 +80,8 @@ public class ObservationController extends HttpServlet { { Observation observation = new Observation(); request.setAttribute("observation", observation); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); List<Organism> allOrganisms = em.createNamedQuery("Organism.findAll").getResultList(); request.setAttribute("allOrganisms", allOrganisms); // Hierarchy categories @@ -152,7 +154,6 @@ public class ObservationController extends HttpServlet { observation.setLocation(formValidation.getFormField("location").getValueAsPointWGS84()); observation = SessionControllerGetter.getObservationBean().storeObservation(observation); - // Redirecting to form // Redirect to form response.sendRedirect(new StringBuilder("http://") .append(ServletUtil.getServerName(request)) diff --git a/src/main/java/no/bioforsk/vips/logic/controller/servlet/PointOfInterestController.java b/src/main/java/no/bioforsk/vips/logic/controller/servlet/PointOfInterestController.java index 2a8bc7cc24e46cf2c2f2ceacfa9572ab31310503..e996fbe40dfec86e8152eff3288e4b17679aed4f 100644 --- a/src/main/java/no/bioforsk/vips/logic/controller/servlet/PointOfInterestController.java +++ b/src/main/java/no/bioforsk/vips/logic/controller/servlet/PointOfInterestController.java @@ -22,14 +22,26 @@ package no.bioforsk.vips.logic.controller.servlet; import java.io.IOException; import java.util.List; import java.util.TimeZone; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import no.bioforsk.vips.logic.entity.Country; import no.bioforsk.vips.logic.entity.PointOfInterest; +import no.bioforsk.vips.logic.entity.PointOfInterestType; import no.bioforsk.vips.logic.entity.PointOfInterestWeatherStation; +import no.bioforsk.vips.logic.entity.VipsLogicRole; import no.bioforsk.vips.logic.entity.VipsLogicUser; +import no.bioforsk.vips.logic.entity.WeatherStationDataSource; +import no.bioforsk.vips.logic.util.Globals; import no.bioforsk.vips.logic.util.SessionControllerGetter; +import no.bioforsk.vips.util.ExceptionUtil; +import no.bioforsk.vips.util.ServletUtil; +import no.bioforsk.web.forms.FormValidation; +import no.bioforsk.web.forms.FormValidationException; +import no.bioforsk.web.forms.FormValidator; /** * Handles transactions for POIs @@ -37,7 +49,8 @@ import no.bioforsk.vips.logic.util.SessionControllerGetter; * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> */ public class PointOfInterestController extends HttpServlet { - + @PersistenceContext(unitName="VIPSLogic-PU") + EntityManager em; /** * Processes requests for both HTTP * <code>GET</code> and @@ -53,7 +66,7 @@ public class PointOfInterestController extends HttpServlet { request.getSession().removeAttribute("weatherStations"); request.getSession().removeAttribute("weatherStation"); - + String action = request.getParameter("action"); VipsLogicUser user = (VipsLogicUser) request.getSession().getAttribute("user"); /* @@ -66,46 +79,182 @@ public class PointOfInterestController extends HttpServlet { if(request.getServletPath().equals("/weatherStation")) { - // Decide if requsted view is list view or single view - String pointOfInterestId = request.getParameter("pointOfInterestId"); - // List view - if(pointOfInterestId == null) + if(action == null) { - List<PointOfInterestWeatherStation> weatherStations; - if(user.isSuperUser()){ - weatherStations = SessionControllerGetter.getPointOfInterestBean().getAllWeatherStations(); + // Decide if requested view is list view or single view + String pointOfInterestId = request.getParameter("pointOfInterestId"); + // List view + if(pointOfInterestId == null) + { + List<PointOfInterestWeatherStation> weatherStations; + if(user.isSuperUser()){ + weatherStations = SessionControllerGetter.getPointOfInterestBean().getAllWeatherStations(); + } + else + { + weatherStations = SessionControllerGetter.getPointOfInterestBean().getWeatherstationsForOrganization(user.getOrganizationId()); + } + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + request.getSession().setAttribute("weatherStations", weatherStations); + request.getRequestDispatcher("/weatherstationList.ftl").forward(request, response); } + // Single view else { - weatherStations = SessionControllerGetter.getPointOfInterestBean().getWeatherstationsForUser(user.getUserId()); + PointOfInterest weatherStation = SessionControllerGetter.getPointOfInterestBean().getPointOfInterest(Integer.valueOf(pointOfInterestId)); + if(weatherStation instanceof PointOfInterestWeatherStation) + { + PointOfInterestWeatherStation stationWithDataSource = (PointOfInterestWeatherStation) weatherStation; + request.getSession().setAttribute("weatherStation", stationWithDataSource); + } + else + { + request.getSession().setAttribute("weatherStation", weatherStation); + } + request.getSession().setAttribute("availableTimeZones", TimeZone.getAvailableIDs()); + request.getSession().setAttribute("defaultTimeZoneId", user.getOrganizationId().getDefaultTimeZone()); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + + request.getRequestDispatcher("/weatherstationView.ftl").forward(request, response); + } - request.getSession().setAttribute("weatherStations", weatherStations); - request.getRequestDispatcher("/weatherstationlist.ftl").forward(request, response); } - // Single view - else + else if(action.equals("newWeatherStationForm")) { - PointOfInterest weatherStation = SessionControllerGetter.getPointOfInterestBean().getPointOfInterest(Integer.valueOf(pointOfInterestId)); - if(weatherStation instanceof PointOfInterestWeatherStation) + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) { - PointOfInterestWeatherStation stationWithDataSource = (PointOfInterestWeatherStation) weatherStation; - request.getSession().setAttribute("weatherStation", stationWithDataSource); + try + { + PointOfInterest weatherStation = new PointOfInterestWeatherStation(); + request.getSession().setAttribute("weatherStation", weatherStation); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + request.getSession().setAttribute("dataSources", SessionControllerGetter.getPointOfInterestBean().getWeatherStationDataSources()); + request.getSession().setAttribute("availableTimeZones", TimeZone.getAvailableIDs()); + request.getSession().setAttribute("defaultTimeZoneId", user.getOrganizationId().getDefaultTimeZone()); + request.getSession().setAttribute("availableCountries", em.createNamedQuery("Country.findAll").getResultList()); + request.getSession().setAttribute("defaultCountryCode", user.getOrganizationId().getCountryCode().getCountryCode()); + if(user.isSuperUser()) + { + request.getSession().setAttribute("users", em.createNamedQuery("VipsLogicUser.findAll", VipsLogicUser.class).getResultList()); + } + request.getRequestDispatcher("/weatherstationForm.ftl").forward(request, response); + } + catch(NullPointerException | NumberFormatException ex) + { + response.sendError(500, ExceptionUtil.getStackTrace(ex)); + } } else { - request.getSession().setAttribute("weatherStation", weatherStation); + response.sendError(403,"Access not authorized"); // HTTP Forbidden } - request.getSession().setAttribute("availableTimeZones", TimeZone.getAvailableIDs()); - request.getSession().setAttribute("defaultTimeZoneId", TimeZone.getDefault().getID()); - - // Dispatch to read or write view? - if(request.getPathInfo() != null && request.getPathInfo().equals("/edit")) + } + // Authorization: ORGANIZATION ADMIN or SUPERUSER + else if(action.equals("editWeatherStationForm")) + { + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) { - request.getRequestDispatcher("/weatherstationForm.ftl").forward(request, response); + try + { + Integer pointOfInterestId = Integer.valueOf(request.getParameter("pointOfInterestId")); + PointOfInterest weatherStation = em.find(PointOfInterest.class, pointOfInterestId); + request.getSession().setAttribute("weatherStation", weatherStation); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + request.getSession().setAttribute("dataSources", SessionControllerGetter.getPointOfInterestBean().getWeatherStationDataSources()); + request.getSession().setAttribute("availableTimeZones", TimeZone.getAvailableIDs()); + request.getSession().setAttribute("defaultTimeZoneId", user.getOrganizationId().getDefaultTimeZone()); + request.getSession().setAttribute("availableCountries", em.createNamedQuery("Country.findAll").getResultList()); + request.getSession().setAttribute("defaultCountryCode", user.getOrganizationId().getCountryCode().getCountryCode()); + if(user.isSuperUser()) + { + request.getSession().setAttribute("users", em.createNamedQuery("VipsLogicUser.findAll", VipsLogicUser.class).getResultList()); + } + request.getRequestDispatcher("/weatherstationForm.ftl").forward(request, response); + } + catch(NullPointerException | NumberFormatException ex) + { + response.sendError(500, ExceptionUtil.getStackTrace(ex)); + } + } + else + { + response.sendError(403,"Access not authorized"); // HTTP Forbidden + } + } + // Authorization: ORGANIZATION ADMIN or SUPERUSER + else if(action.equals("weatherStationFormSubmit")) + { + if(SessionControllerGetter.getUserBean().authorizeUser(user, VipsLogicRole.ORGANIZATION_ADMINISTRATOR, VipsLogicRole.SUPERUSER)) + { + try + { + Integer pointOfInterestId = Integer.valueOf(request.getParameter("pointOfInterestId")); + PointOfInterestWeatherStation weatherStation = pointOfInterestId > 0 ? + em.find(PointOfInterestWeatherStation.class, pointOfInterestId) + : new PointOfInterestWeatherStation(); + FormValidation formValidation = FormValidator.validateForm("weatherStationForm", request, getServletContext()); + if(formValidation.isValid()) + { + // Set values + weatherStation.setName(formValidation.getFormField("name").getWebValue()); + weatherStation.setAltitude(formValidation.getFormField("altitude").getValueAsDouble()); + weatherStation.setLongitude(formValidation.getFormField("location").getValueAsPointWGS84().getX()); + weatherStation.setLatitude(formValidation.getFormField("location").getValueAsPointWGS84().getY()); + weatherStation.setWeatherStationDataSourceId(em.find(WeatherStationDataSource.class, formValidation.getFormField("weatherStationDataSourceId").getValueAsInteger())); + weatherStation.setWeatherStationRemoteId(formValidation.getFormField("weatherStationRemoteId").getWebValue()); + weatherStation.setTimeZone(formValidation.getFormField("timeZone").getWebValue()); + weatherStation.setCountryCode(em.find(Country.class, formValidation.getFormField("countryCode").getWebValue())); + if(weatherStation.getPointOfInterestType() == null) + { + weatherStation.setPointOfInterestType(em.find(PointOfInterestType.class, Globals.POI_TYPE_WEATHERSTATION)); + } + // If userId is set from form, always update + if(user.isSuperUser() && !formValidation.getFormField("userId").isEmpty()) + { + weatherStation.setUserId(em.find(VipsLogicUser.class, formValidation.getFormField("userId").getValueAsInteger())); + } + // If user is not set, use current user + else if(weatherStation.getUserId() == null) + { + weatherStation.setUserId(user); + } + // Store + weatherStation = SessionControllerGetter.getPointOfInterestBean().storeWeatherStation(weatherStation); + + // Redirect to form + response.sendRedirect(new StringBuilder("http://") + .append(ServletUtil.getServerName(request)) + .append("/weatherStation?action=editWeatherStationForm&pointOfInterestId=").append(weatherStation.getPointOfInterestId()) + .append("&messageKey=").append("weatherStationStored").toString() + + ); + } + else + { + request.setAttribute("formValidation", formValidation); + request.setAttribute("weatherStation", weatherStation); + request.setAttribute("defaultMapCenter",user.getOrganizationId().getDefaultMapCenter()); + request.setAttribute("defaultMapZoom", user.getOrganizationId().getDefaultMapZoom()); + request.getSession().setAttribute("dataSources", SessionControllerGetter.getPointOfInterestBean().getWeatherStationDataSources()); + request.getSession().setAttribute("availableTimeZones", TimeZone.getAvailableIDs()); + request.getSession().setAttribute("defaultTimeZoneId", user.getOrganizationId().getDefaultTimeZone()); + request.getSession().setAttribute("availableCountries", em.createNamedQuery("Country.findAll").getResultList()); + request.getSession().setAttribute("defaultCountryCode", user.getOrganizationId().getCountryCode().getCountryCode()); + request.getRequestDispatcher("/weatherstationForm.ftl").forward(request, response); + } + } + catch(NullPointerException | NumberFormatException | FormValidationException ex) + { + response.sendError(500, ExceptionUtil.getStackTrace(ex)); + } } else { - request.getRequestDispatcher("/weatherstation.ftl").forward(request, response); + response.sendError(403,"Access not authorized"); // HTTP Forbidden } } } diff --git a/src/main/java/no/bioforsk/vips/logic/controller/session/PointOfInterestBean.java b/src/main/java/no/bioforsk/vips/logic/controller/session/PointOfInterestBean.java index 5887d6f9b91ccddeab7282fa1299d91eeb3e682d..c09d6d831f5d28e7ddd7d5086addc484ddc7e5c4 100644 --- a/src/main/java/no/bioforsk/vips/logic/controller/session/PointOfInterestBean.java +++ b/src/main/java/no/bioforsk/vips/logic/controller/session/PointOfInterestBean.java @@ -19,14 +19,28 @@ package no.bioforsk.vips.logic.controller.session; +import de.micromata.opengis.kml.v_2_2_0.Coordinate; +import de.micromata.opengis.kml.v_2_2_0.Document; +import de.micromata.opengis.kml.v_2_2_0.Kml; +import de.micromata.opengis.kml.v_2_2_0.KmlFactory; +import de.micromata.opengis.kml.v_2_2_0.Placemark; +import de.micromata.opengis.kml.v_2_2_0.Point; +import java.util.HashSet; import java.util.List; +import java.util.ResourceBundle; +import java.util.Set; import javax.ejb.LocalBean; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.Query; +import no.bioforsk.vips.logic.entity.Organization; import no.bioforsk.vips.logic.entity.PointOfInterest; import no.bioforsk.vips.logic.entity.PointOfInterestWeatherStation; +import no.bioforsk.vips.logic.entity.UserPointOfInterest; +import no.bioforsk.vips.logic.entity.UserPointOfInterestPK; +import no.bioforsk.vips.logic.entity.VipsLogicUser; +import no.bioforsk.vips.logic.entity.WeatherStationDataSource; import no.bioforsk.vips.logic.util.Globals; @@ -79,4 +93,78 @@ public class PointOfInterestBean { return (PointOfInterest) q.getSingleResult(); } + + public Kml getWeatherstationsForOrganization(Integer organizationId, Integer excludeWeatherStationId, Integer highlightWeatherStationId, String serverName, ResourceBundle i18nBundle) { + String iconPath = "http://" + serverName + "/public/images/"; + Organization organization = em.find(Organization.class, organizationId); + // Initialization + final Kml kml = KmlFactory.createKml(); + final Document document = kml.createAndSetDocument() + .withName("Weather stations").withDescription("Weather stations for "); + + document.createAndAddStyle() + .withId("weatherstation_icon") + .createAndSetIconStyle() + .withScale(0.55) + .createAndSetIcon() + .withHref(iconPath + "dot_blue.png"); + + document.createAndAddStyle() + .withId("weatherstation_icon_highlighted") + .createAndSetIconStyle() + .withScale(0.55) + .createAndSetIcon() + .withHref(iconPath + "anemometer_mono.png"); + + List<PointOfInterestWeatherStation> weatherStations = this.getWeatherstationsForOrganization(organization); + String description = ""; + for(PointOfInterestWeatherStation weatherStation:weatherStations) + { + if(excludeWeatherStationId != null && excludeWeatherStationId.equals(weatherStation.getPointOfInterestId())) + { + continue; + } + + String styleUrl = "#weatherstation_icon" + + (highlightWeatherStationId != null && highlightWeatherStationId.equals(weatherStation.getPointOfInterestId()) ? "_highlighted" :""); + + if(weatherStation instanceof PointOfInterestWeatherStation) + { + description = i18nBundle.getString("dataSourceName") + + ": <a href=\"" + weatherStation.getWeatherStationDataSourceId().getUri() + "\" target=\"new\">" + + weatherStation.getWeatherStationDataSourceId().getName() + + "</a>"; + } + final Placemark placemark = document.createAndAddPlacemark() + .withName(weatherStation.getName()) + .withDescription(description) + .withStyleUrl(styleUrl) + .withId(weatherStation.getPointOfInterestId().toString()); + + + final Point point = placemark.createAndSetPoint(); + List<Coordinate> coord = point.createAndSetCoordinates(); + coord.add(new Coordinate( + weatherStation.getLongitude(), + weatherStation.getLatitude(), + weatherStation.getAltitude() != null ? weatherStation.getAltitude() : 0 + )); + } + return kml; + } + + public List<WeatherStationDataSource> getWeatherStationDataSources() { + return em.createNamedQuery("WeatherStationDataSource.findAll").getResultList(); + } + + public PointOfInterestWeatherStation storeWeatherStation(PointOfInterestWeatherStation weatherStation) { + weatherStation = em.merge(weatherStation); + return weatherStation; + } + + public List<PointOfInterestWeatherStation> getWeatherstationsForOrganization(Organization organization) { + return em.createNamedQuery("PointOfInterestWeatherStation.findByOrganizationId", PointOfInterestWeatherStation.class) + .setParameter("organizationId", organization) + .getResultList(); + } } diff --git a/src/main/java/no/bioforsk/vips/logic/entity/Country.java b/src/main/java/no/bioforsk/vips/logic/entity/Country.java index 07cdc5e4c395082d48daef92b6887802f6a9632c..1ed4cf116c4ef05df183671418960c5f183015d2 100644 --- a/src/main/java/no/bioforsk/vips/logic/entity/Country.java +++ b/src/main/java/no/bioforsk/vips/logic/entity/Country.java @@ -20,6 +20,7 @@ package no.bioforsk.vips.logic.entity; import java.io.Serializable; +import java.util.Locale; import java.util.Set; import javax.persistence.Basic; import javax.persistence.Column; @@ -32,6 +33,7 @@ import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; import javax.persistence.OneToMany; import javax.persistence.Table; +import javax.persistence.Transient; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlRootElement; @@ -50,19 +52,11 @@ import org.codehaus.jackson.annotate.JsonIgnore; @NamedQuery(name = "Country.findByCountryCode", query = "SELECT c FROM Country c WHERE c.countryCode = :countryCode")}) public class Country implements Serializable { private static final long serialVersionUID = 1L; - @Id - @Basic(optional = false) @NotNull @Size(min = 1, max = 2) - @Column(name = "country_code") private String countryCode; - @JoinTable(name = "external_resource_validity", joinColumns = { - @JoinColumn(name = "country_code", referencedColumnName = "country_code")}, inverseJoinColumns = { - @JoinColumn(name = "external_resource_id", referencedColumnName = "external_resource_id")}) - @ManyToMany - private Set<ExternalResource> externalResourceSet; - @OneToMany(mappedBy = "countryCode") - private Set<PointOfInterest> pointOfInterestSet; + private Set<ExternalResource> externalResourceSet; + private Set<PointOfInterest> pointOfInterestSet; public Country() { } @@ -71,6 +65,9 @@ public class Country implements Serializable { this.countryCode = countryCode; } +@Id + @Basic(optional = false) + @Column(name = "country_code") public String getCountryCode() { return countryCode; } @@ -79,6 +76,10 @@ public class Country implements Serializable { this.countryCode = countryCode; } +@JoinTable(name = "external_resource_validity", joinColumns = { + @JoinColumn(name = "country_code", referencedColumnName = "country_code")}, inverseJoinColumns = { + @JoinColumn(name = "external_resource_id", referencedColumnName = "external_resource_id")}) + @ManyToMany @XmlTransient @JsonIgnore public Set<ExternalResource> getExternalResourceSet() { @@ -89,6 +90,7 @@ public class Country implements Serializable { this.externalResourceSet = externalResourceSet; } +@OneToMany(mappedBy = "countryCode") @XmlTransient @JsonIgnore public Set<PointOfInterest> getPointOfInterestSet() { @@ -124,4 +126,10 @@ public class Country implements Serializable { return "no.bioforsk.vips.logic.entity.Country[ countryCode=" + countryCode + " ]"; } + + public String getCountryName(String language) + { + Locale locale = new Locale(language, this.countryCode); + return locale.getDisplayCountry(); + } } diff --git a/src/main/java/no/bioforsk/vips/logic/entity/Organization.java b/src/main/java/no/bioforsk/vips/logic/entity/Organization.java index 46a5ebec5d3f3e55ec31e43cde65756d916238d7..4020ab91ac0980bea4bda20d9fcf071675b67fce 100644 --- a/src/main/java/no/bioforsk/vips/logic/entity/Organization.java +++ b/src/main/java/no/bioforsk/vips/logic/entity/Organization.java @@ -19,6 +19,7 @@ package no.bioforsk.vips.logic.entity; +import com.vividsolutions.jts.geom.Point; import java.io.Serializable; import java.util.Set; import javax.persistence.Basic; @@ -37,6 +38,7 @@ import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlTransient; import org.codehaus.jackson.annotate.JsonIgnore; +import org.hibernate.annotations.Type; /** * @copyright 2013 <a href="http://www.bioforsk.no/">Bioforsk</a> @@ -71,6 +73,14 @@ public class Organization implements Serializable { @Size(max = 63) @Column(name = "postal_code") private String postalCode; + @Column(name = "default_map_zoom") + private Integer defaultMapZoom; + @Column(name = "default_time_zone") + private String defaultTimeZone; + @JsonIgnore + @Type(type = "org.hibernate.spatial.GeometryType") + @Column(name = "default_map_center", columnDefinition = "Geometry") + private Point defaultMapCenter; @OneToMany(mappedBy = "parentOrganizationId") private Set<Organization> organizationSet; @JoinColumn(name = "parent_organization_id", referencedColumnName = "organization_id") @@ -207,4 +217,46 @@ public class Organization implements Serializable { this.defaultLocale = defaultLocale; } + /** + * @return the defaultMapZoom + */ + public Integer getDefaultMapZoom() { + return defaultMapZoom; + } + + /** + * @param defaultMapZoom the defaultMapZoom to set + */ + public void setDefaultMapZoom(Integer defaultMapZoom) { + this.defaultMapZoom = defaultMapZoom; + } + + /** + * @return the defaultMapCenter + */ + public Point getDefaultMapCenter() { + return defaultMapCenter; + } + + /** + * @param defaultMapCenter the defaultMapCenter to set + */ + public void setDefaultMapCenter(Point defaultMapCenter) { + this.defaultMapCenter = defaultMapCenter; + } + + /** + * @return the defaultTimeZone + */ + public String getDefaultTimeZone() { + return defaultTimeZone; + } + + /** + * @param defaultTimeZone the defaultTimeZone to set + */ + public void setDefaultTimeZone(String defaultTimeZone) { + this.defaultTimeZone = defaultTimeZone; + } + } diff --git a/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterest.java b/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterest.java index 1c06c6009e7716bae0fa0217ea7e25275d80c525..672ccfcd20ed0044205cfabe33322e2d7a08db55 100644 --- a/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterest.java +++ b/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterest.java @@ -21,9 +21,7 @@ package no.bioforsk.vips.logic.entity; import java.io.Serializable; import java.util.HashMap; import java.util.Map; -import java.util.Set; import javax.persistence.Basic; -import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.DiscriminatorColumn; import javax.persistence.Entity; @@ -36,12 +34,10 @@ import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; -import javax.persistence.OneToMany; import javax.persistence.Table; import javax.persistence.Transient; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlRootElement; -import javax.xml.bind.annotation.XmlTransient; import org.codehaus.jackson.annotate.JsonIgnore; /** @@ -60,7 +56,8 @@ import org.codehaus.jackson.annotate.JsonIgnore; @NamedQuery(name = "PointOfInterest.findByName", query = "SELECT p FROM PointOfInterest p WHERE p.name = :name"), @NamedQuery(name = "PointOfInterest.findByLongitude", query = "SELECT p FROM PointOfInterest p WHERE p.longitude = :longitude"), @NamedQuery(name = "PointOfInterest.findByLatitude", query = "SELECT p FROM PointOfInterest p WHERE p.latitude = :latitude"), - @NamedQuery(name = "PointOfInterest.findByAltitude", query = "SELECT p FROM PointOfInterest p WHERE p.altitude = :altitude") + @NamedQuery(name = "PointOfInterest.findByAltitude", query = "SELECT p FROM PointOfInterest p WHERE p.altitude = :altitude"), + @NamedQuery(name = "PointOfInterest.findByUserId", query = "SELECT p FROM PointOfInterest p WHERE p.userId = :userId") }) public class PointOfInterest implements Serializable, Comparable { @@ -83,8 +80,9 @@ public class PointOfInterest implements Serializable, Comparable { private Double latitude; @Column(name = "altitude") private Double altitude; - @OneToMany(cascade = CascadeType.ALL, mappedBy = "pointOfInterest") - private Set<UserPointOfInterest> userPointOfInterestSet; + @JoinColumn(name = "user_id", referencedColumnName = "user_id") + @ManyToOne + private VipsLogicUser userId; @JoinColumn(name = "country_code", referencedColumnName = "country_code") @ManyToOne private Country countryCode; @@ -144,16 +142,7 @@ public class PointOfInterest implements Serializable, Comparable { this.altitude = altitude; } - @XmlTransient - @JsonIgnore - public Set<UserPointOfInterest> getUserPointOfInterestSet() { - return userPointOfInterestSet; - } - - public void setUserPointOfInterestSet(Set<UserPointOfInterest> userPointOfInterestSet) { - this.userPointOfInterestSet = userPointOfInterestSet; - } - + public Country getCountryCode() { return countryCode; } @@ -249,4 +238,19 @@ public class PointOfInterest implements Serializable, Comparable { public void setProperties(Map<String,Object> properties) { this.properties = properties; } + + /** + * @return the userId + */ + @JsonIgnore + public VipsLogicUser getUserId() { + return userId; + } + + /** + * @param userId the userId to set + */ + public void setUserId(VipsLogicUser userId) { + this.userId = userId; + } } diff --git a/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterestWeatherStation.java b/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterestWeatherStation.java index 5c275e7099202e465312442462c700a456f97eb9..7254b146a42bfb7dd4416fecd5c224d0eb12572f 100644 --- a/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterestWeatherStation.java +++ b/src/main/java/no/bioforsk/vips/logic/entity/PointOfInterestWeatherStation.java @@ -45,7 +45,8 @@ import org.codehaus.jackson.annotate.JsonIgnore; @NamedQueries({ @NamedQuery(name = "PointOfInterestWeatherStation.findAll", query = "SELECT p FROM PointOfInterest p WHERE p.pointOfInterestType.pointOfInterestTypeId=1"), @NamedQuery(name = "PointOfInterestWeatherStation.findByPointOfInterestId", query = "SELECT p FROM PointOfInterest p WHERE p.pointOfInterestId = :pointOfInterestId AND p.pointOfInterestType.pointOfInterestTypeId=1"), - @NamedQuery(name = "PointOfInterestWeatherStation.findByUserId", query = "SELECT p FROM PointOfInterest p WHERE p.pointOfInterestType.pointOfInterestTypeId=1 AND p.pointOfInterestId IN(SELECT up.pointOfInterest.pointOfInterestId FROM UserPointOfInterest up WHERE up.userPointOfInterestPK.userId = :userId)") + @NamedQuery(name = "PointOfInterestWeatherStation.findByOrganizationId", query = "SELECT p FROM PointOfInterestWeatherStation p WHERE p.pointOfInterestType.pointOfInterestTypeId=1 AND p.userId IN(SELECT u.userId FROM VipsLogicUser u WHERE u.organizationId=:organizationId)"), + @NamedQuery(name = "PointOfInterestWeatherStation.findByUserId", query = "SELECT p FROM PointOfInterestWeatherStation p WHERE p.pointOfInterestType.pointOfInterestTypeId=1 AND p.userId = :userId") }) public class PointOfInterestWeatherStation extends PointOfInterest implements Serializable { @Size(max = 255) diff --git a/src/main/java/no/bioforsk/vips/logic/service/LogicService.java b/src/main/java/no/bioforsk/vips/logic/service/LogicService.java index a26880a16d69d8c131a6696e5dc6517acce34796..5a4f0b002c86cdf321d94b046b297fe5d97fe6c6 100644 --- a/src/main/java/no/bioforsk/vips/logic/service/LogicService.java +++ b/src/main/java/no/bioforsk/vips/logic/service/LogicService.java @@ -170,6 +170,15 @@ public class LogicService { return Response.ok().entity(latestResults).build(); } + @GET + @Path("weatherstations/kml/{organizationId}") + @Produces("application/vnd.google-earth.kml+xml;charset=utf-8") + public Response getWeatherStations(@QueryParam("excludeWeatherStationId") Integer excludeWeatherStationId, @QueryParam("highlightWeatherStationId") Integer highlightWeatherStationId, @PathParam("organizationId") Integer organizationId) + { + Kml retVal = SessionControllerGetter.getPointOfInterestBean().getWeatherstationsForOrganization(organizationId, excludeWeatherStationId, highlightWeatherStationId, ServletUtil.getServerName(httpServletRequest), SessionLocaleUtil.getI18nBundle(httpServletRequest)); + return Response.ok().entity(retVal).build(); + } + @GET @Path("organism/list") @Produces("application/json;charset=UTF-8") diff --git a/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts.properties b/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts.properties index 82a5d0bd1bd95c69e896ff36d1e25e0b0c179f91..5f4b9ac0e9694d6f15857ba476f8588ec0e0b47d 100644 --- a/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts.properties +++ b/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts.properties @@ -210,3 +210,9 @@ informAdminOfConfirmedEmailSubject=New user has confirmed email and is now ready informAdminOfConfirmedEmailBody=The user''s last name is {0}. Follow this link to edit this user: {1} sendUserApprovalConfirmationSubject=Your user account has been approved sendUserApprovalConfirmationBody=We are happy to confirm that your user account for VIPSLogic has been approved. Please log in here: {0} +dataSourceName=Data source name +newWeatherStation=New weather station +meter=Meter +country=Country +editWeatherStation=Edit weather station +toTheTop=To the top diff --git a/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts_no.properties b/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts_no.properties index 17627de0c749b0b5888e70a153c867afb2eec989..75ae7830fd86c3478c83621c824f461f112b3b88 100644 --- a/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts_no.properties +++ b/src/main/resources/no/bioforsk/vips/logic/i18n/vipslogictexts_no.properties @@ -210,3 +210,9 @@ informAdminOfConfirmedEmailSubject=Ny bruker har bekreftet sin e-postadresse og informAdminOfConfirmedEmailBody=Brukerens etternavn er {0}. F\u00f8lg denne lenken for \u00e5 redigere/godkjenne: {1} sendUserApprovalConfirmationSubject=Din brukerkonto har blitt godkjent sendUserApprovalConfirmationBody=Vi har gleden av \u00e5 bekrefte at din brukerkonto hos VIPSLogic er blitt godkjent. Vennligst logg in her: {0} +dataSourceName=Datakildenavn +newWeatherStation=Ny m\u00e5lestasjon +meter=Meter +country=Land +editWeatherStation=Rediger m\u00e5lestasjon +toTheTop=Helt til topps diff --git a/src/main/webapp/css/vipslogic.css b/src/main/webapp/css/vipslogic.css index c0e4006547349da3ecbbdf95de51d571f2970214..72d6f03c3d79ea2f116e2490a022225fc2987366 100644 --- a/src/main/webapp/css/vipslogic.css +++ b/src/main/webapp/css/vipslogic.css @@ -70,7 +70,7 @@ legend { display: block; } -#observationFormMap{ +#observationFormMap, #weatherStationListMap, #weatherStationFormMap, #weatherStationViewMap{ height: 400px; width: 100%; } \ No newline at end of file diff --git a/src/main/webapp/formdefinitions/weatherStationForm.json b/src/main/webapp/formdefinitions/weatherStationForm.json new file mode 100644 index 0000000000000000000000000000000000000000..449fa851a359ee29a5a0e74494186bf861e121e1 --- /dev/null +++ b/src/main/webapp/formdefinitions/weatherStationForm.json @@ -0,0 +1,72 @@ +{ + "_licenseNote": [ + "Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>.", + "", + "This file is part of VIPSLogic.", + "VIPSLogic is free software: you can redistribute it and/or modify", + "it under the terms of the GNU Affero General Public License as published by", + "the Free Software Foundation, either version 3 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", + "GNU Affero General Public License for more details.", + "", + "You should have received a copy of the GNU Affero General Public License", + "along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>." + ], + "_comment" : "Structure of the weatherStationForm and how to validate it", + "fields": [ + { + "name" : "pointOfInterestId", + "dataType" : "INTEGER", + "required" : true + }, + { + "name" : "name", + "dataType" : "STRING", + "required" : true + }, + { + "name" : "weatherStationDataSourceId", + "dataType" : "INTEGER", + "fieldType" : "SELECT_SINGLE", + "required" : true, + "nullValue" : "-1" + }, + { + "name" : "weatherStationRemoteId", + "dataType" : "STRING", + "required" : true + }, + { + "name" : "location", + "dataType" : "POINT_WGS84", + "required" : true + }, + { + "name" : "altitude", + "dataType" : "DOUBLE", + "required" : false + }, + { + "name" : "timeZone", + "dataType" : "STRING", + "fieldType" : "SELECT_SINGLE", + "required" : true + }, + { + "name" : "countryCode", + "dataType" : "STRING", + "fieldType" : "SELECT_SINGLE", + "required" : true + }, + { + "name" : "userId", + "dataType" : "INTEGER", + "required" : false + } + + ] +} diff --git a/src/main/webapp/js/constants.js b/src/main/webapp/js/constants.js new file mode 100644 index 0000000000000000000000000000000000000000..7d7a94fca0490b3c3ffdd412e7aced8193bae691 --- /dev/null +++ b/src/main/webapp/js/constants.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. + * + */ + + +/* + * Constants for maps + */ +var mapConstants = { + // The attribution shown in the corner of the map + MAP_ATTRIBUTION : "© <a href='http://www.openstreetmap.org'>OpenStreetMap</a> contributors" +}; \ No newline at end of file diff --git a/src/main/webapp/js/observationFormMap.js b/src/main/webapp/js/observationFormMap.js index 616ec67848f69b4166e7bf3263a85bdf77addea8..53a843e44e0c56ce606ea8917667d5dbf40e2407 100644 --- a/src/main/webapp/js/observationFormMap.js +++ b/src/main/webapp/js/observationFormMap.js @@ -32,7 +32,13 @@ function initMap(center, zoomLevel, displayMarker) { // Background layer is OpenStreetMap var backgroundLayer = new ol.layer.Tile({ - source: new ol.source.OSM() + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: mapConstants.MAP_ATTRIBUTION + }) + ] + }) }); diff --git a/src/main/webapp/js/validateForm.js b/src/main/webapp/js/validateForm.js index 1cc79f8e4b38a14f3b1619036c3bfcc331faba72..066d7603a85dc335466fab829b1290f1216aa94f 100644 --- a/src/main/webapp/js/validateForm.js +++ b/src/main/webapp/js/validateForm.js @@ -188,12 +188,12 @@ function validateField(fieldEl, formDefinitionKey) $.getJSON( "/formdefinitions/" + theForm.id + ".json") .done(function(json){ formDefinitions[theForm.id] = json; - validateFieldActual(fieldEl, theForm, formDefinitionKey); + return validateFieldActual(fieldEl, theForm, formDefinitionKey); }); } else { - validateFieldActual(fieldEl, theForm, formDefinitionKey); + return validateFieldActual(fieldEl, theForm, formDefinitionKey); } } @@ -231,7 +231,7 @@ function getValidationOutputEl(fieldEl, theForm) * @param {Element} fieldEl the form element to be validated * @param {Element} theForm the form * @param {String} formDefinitionKey optional key to a formdefinition for a part of the form that has been dynamically added to the main form - * @returns {void} + * @returns {boolean} */ function validateFieldActual(fieldEl, theForm, formDefinitionKey) { diff --git a/src/main/webapp/js/weatherStationFormMap.js b/src/main/webapp/js/weatherStationFormMap.js new file mode 100644 index 0000000000000000000000000000000000000000..38b71cce713af4c21ffbc138edea77d3863f137e --- /dev/null +++ b/src/main/webapp/js/weatherStationFormMap.js @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* + * KML layer map with weather station information + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ + +// Keeping the map and station marker globally available +var map; +var stationMarker; + +/** + * + * @param {ol.Coordinate} center - coordinates for the map's center (WGS84) + * @param {int} zoomLevel - the zoom level (1-15, 1 is world wide view, 15 is greatest zoom) + * * @param {int} currentWeatherStationId - the current weather station id + * @returns {void} + */ +function initMap(center, zoomLevel, currentWeatherStationId) +{ + // Background layer is OpenStreetMap + var backgroundLayer = new ol.layer.Tile({ + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: mapConstants.MAP_ATTRIBUTION + }) + ] + }) + }); + + // The weather station layer + var weatherStationLayer = new ol.layer.Vector({ + source: new ol.source.KML({ + url: "/rest/weatherstations/kml/2" + (currentWeatherStationId > 0 ? "?excludeWeatherStationId=" + currentWeatherStationId : ""), + projection: "EPSG:3857" + }) + }); + + + // Layer for popup + var popOverlay = new ol.Overlay({ + element: document.getElementById("popover") + }); + + // Creating the map + map = new ol.Map({ + target: 'weatherStationFormMap', + layers: [backgroundLayer,weatherStationLayer], + 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.View2D({ + center: centerPosition, + zoom:zoomLevel + }); + map.setView(view); + + // Marker overlay + stationMarker = new ol.Overlay({ + position: currentWeatherStationId !== null ? centerPosition : undefined, + positioning: 'bottom-center', + element: document.getElementById('stationMarker'), + stopEvent: false + }); + + map.addOverlay(stationMarker); + + + // Listening for single clicks, position observation pin and updating form element + map.on(['singleclick'], function(evt) { + updateLocationPosition(evt.coordinate); + }); + + + + + + + + +} + +function updateLocationPosition(coordinate) +{ + var locationPosition = ol.coordinate.toStringXY(ol.proj.transform(coordinate, map.getView().getProjection().getCode(), 'EPSG:4326'),4); + // Set/move location pin + stationMarker.setPosition(coordinate); + // Update form field "location" + var locationEl = document.getElementById("location"); + //console.log(locationEl); + locationEl.value=locationPosition; + + // Adding a little animation + $("#location").animate({borderWidth: "4"},500, function(){ + $("#location").animate({borderWidth: "1"},500, function(){}); + }); +} + +/** + * Places the station marker on the coordinates given in Location input field, + * and centers the map around these coordinates + */ +function updateMarkerPosition() +{ + var locationEl = document.getElementById("location"); + + var coordinate = locationEl.value.split(","); + coordinate[0] = parseFloat(coordinate[0]); + coordinate[1] = parseFloat(coordinate[1]); + //console.log(coordinate); + var centerPosition = ol.proj.transform(coordinate, 'EPSG:4326', map.getView().getProjection().getCode()); + + stationMarker.setPosition(centerPosition); + + // 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.View2D({ + center: centerPosition, + zoom:10 + }); + map.setView(view); +} + diff --git a/src/main/webapp/js/weatherStationListMap.js b/src/main/webapp/js/weatherStationListMap.js new file mode 100644 index 0000000000000000000000000000000000000000..f3ad172fdff9c810ce06ccde714379d0ad017c34 --- /dev/null +++ b/src/main/webapp/js/weatherStationListMap.js @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* + * KML layer map with weather station information + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ + +/** + * + * @param {ol.Coordinate} center - coordinates for the map's center (WGS84) + * @param {int} zoomLevel - the zoom level (1-15, 1 is world wide view, 15 is greatest zoom) + * @returns {void} + */ +function initMap(center, zoomLevel) +{ + // Background layer is OpenStreetMap + var backgroundLayer = new ol.layer.Tile({ + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: mapConstants.MAP_ATTRIBUTION + }) + ] + }) + }); + + // The weather station layer + var weatherStationLayer = new ol.layer.Vector({ + source: new ol.source.KML({ + url: "/rest/weatherstations/kml/2", + projection: "EPSG:3857" + }) + }); + + // Layer for popup + var popOverlay = new ol.Overlay({ + element: document.getElementById("popover") + }); + + // Creating the map + var map = new ol.Map({ + target: 'weatherStationListMap', + layers: [backgroundLayer,weatherStationLayer], + 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.View2D({ + center: centerPosition, + zoom:zoomLevel + }); + map.setView(view); + + // Using Bootstrap's popover plugin. See http://getbootstrap.com/javascript/#popovers + var poiDetails = $("#popover"); + + + + // Displays popup with forecasts for a given station + // (if there is a station where the click event is fired) + 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(geometry.getCoordinates()); + poiDetails.popover('destroy'); + // Create the popup, showing it + poiDetails.popover({ + animation: true, + trigger: 'manual', + html: true, + placement: "auto top", + title: "<a href='/weatherStation?pointOfInterestId=" + feature.getId() + "'>" + feature.get("name") + "</a>", + content: feature.get("description") + }); + + poiDetails.popover('show'); + + + } else { + poiDetails.popover('destroy'); + } + + }; + + + // On click, display forecasts in popup + map.on('singleclick', function(evt) { + var pixel = map.getEventPixel(evt.originalEvent); + displayFeatureDetails(pixel); + }); +} + diff --git a/src/main/webapp/js/weatherStationViewMap.js b/src/main/webapp/js/weatherStationViewMap.js new file mode 100644 index 0000000000000000000000000000000000000000..1b0b741e0b52dc9985e5bd9a8644d7b8ae16a36f --- /dev/null +++ b/src/main/webapp/js/weatherStationViewMap.js @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + * + * This file is part of VIPSLogic. + * VIPSLogic is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. + * + */ + +/* + * KML layer map with weather station information + * @author Tor-Einar Skog <tor-einar.skog@bioforsk.no> + */ + +/** + * + * @param {ol.Coordinate} center - coordinates for the map's center (WGS84) + * @param {int} zoomLevel - the zoom level (1-15, 1 is world wide view, 15 is greatest zoom) + * @returns {void} + */ +function initMap(center, zoomLevel, highlightWeatherStationId) +{ + // Background layer is OpenStreetMap + var backgroundLayer = new ol.layer.Tile({ + source: new ol.source.OSM({ + attributions: [ + new ol.Attribution({ + html: mapConstants.MAP_ATTRIBUTION + }) + ] + }) + }); + + // The weather station layer + var weatherStationLayer = new ol.layer.Vector({ + source: new ol.source.KML({ + url: "/rest/weatherstations/kml/2" + (highlightWeatherStationId !== null ? "?highlightWeatherStationId=" + highlightWeatherStationId : ""), + projection: "EPSG:3857" + }) + }); + + // Layer for popup + var popOverlay = new ol.Overlay({ + element: document.getElementById("popover") + }); + + // Creating the map + var map = new ol.Map({ + target: 'weatherStationViewMap', + layers: [backgroundLayer,weatherStationLayer], + 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.View2D({ + center: centerPosition, + zoom:zoomLevel + }); + map.setView(view); + + // Using Bootstrap's popover plugin. See http://getbootstrap.com/javascript/#popovers + var poiDetails = $("#popover"); + + + + // Displays popup with forecasts for a given station + // (if there is a station where the click event is fired) + 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(geometry.getCoordinates()); + poiDetails.popover('destroy'); + // Create the popup, showing it + poiDetails.popover({ + animation: true, + trigger: 'manual', + html: true, + placement: "auto top", + title: "<a href='/weatherStation?pointOfInterestId=" + feature.getId() + "'>" + feature.get("name") + "</a>", + content: feature.get("description") + }); + + poiDetails.popover('show'); + + + } else { + poiDetails.popover('destroy'); + } + + }; + + + // On click, display forecasts in popup + map.on('singleclick', function(evt) { + var pixel = map.getEventPixel(evt.originalEvent); + displayFeatureDetails(pixel); + }); +} + diff --git a/src/main/webapp/public/images/anemometer_mono.png b/src/main/webapp/public/images/anemometer_mono.png new file mode 100644 index 0000000000000000000000000000000000000000..8056a3e729d8e9a3221289774d627a8286f71f55 Binary files /dev/null and b/src/main/webapp/public/images/anemometer_mono.png differ diff --git a/src/main/webapp/templates/forecastConfigurationForm.ftl b/src/main/webapp/templates/forecastConfigurationForm.ftl index eab8c5863801ff08e6f32b0b52f9f12a1b3d8216..82c2f10adbb08586c312d4623593fd42fafcc804 100644 --- a/src/main/webapp/templates/forecastConfigurationForm.ftl +++ b/src/main/webapp/templates/forecastConfigurationForm.ftl @@ -44,6 +44,7 @@ </script> </#macro> <#macro page_contents> + <p><a href="/forecastConfiguration" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> <h1>${i18nBundle.viewForecastConfiguration}</h1> <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>> <#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if> diff --git a/src/main/webapp/templates/messageForm.ftl b/src/main/webapp/templates/messageForm.ftl index c753179027f633ff1af9c823f52b26fdc4a3c8e5..98a1516fbbb7888985945b8d5f1cd732ee779d62 100644 --- a/src/main/webapp/templates/messageForm.ftl +++ b/src/main/webapp/templates/messageForm.ftl @@ -80,6 +80,7 @@ </#macro> <#macro page_contents> + <p><a href="/message" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> <#if message.messageId?has_content> <h1>${i18nBundle.editMessage}</h1> <#else> diff --git a/src/main/webapp/templates/observationForm.ftl b/src/main/webapp/templates/observationForm.ftl index 5dcd1321148801018a7d5809a7df974d05e8d659..8b50245a5973833d6c9d9ae310e061c5e7b3ad00 100644 --- a/src/main/webapp/templates/observationForm.ftl +++ b/src/main/webapp/templates/observationForm.ftl @@ -27,6 +27,7 @@ <script type="text/javascript" src="/js/3rdparty/modernizr_custom.js"></script> <script type="text/javascript" src="/js/3rdparty/moment.min.js"></script> <script type="text/javascript" src="/js/3rdparty/ol.js"></script> + <script type="text/javascript" src="/js/constants.js"></script> <script type="text/javascript" src="/js/resourcebundle.js"></script> <script type="text/javascript" src="/js/validateForm.js"></script> <script type="text/javascript" src="/js/observationFormMap.js"></script> @@ -51,12 +52,13 @@ <#if observation.location?has_content> initMap([${(observation.location.x?c)!""},${(observation.location.y?c)!""}],10,true); <#else> - initMap([14.1,65.4],4,false); + initMap([${defaultMapCenter.x?c},${defaultMapCenter.y?c}],${defaultMapZoom},false); </#if> }); </script> </#macro> <#macro page_contents> + <p><a href="/observation" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> <h1><#if observation.observationId?has_content>${i18nBundle.editObservation}<#else>${i18nBundle.newObservation}</#if></h1> <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>> <#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if> diff --git a/src/main/webapp/templates/organismList.ftl b/src/main/webapp/templates/organismList.ftl index 4dde4687083c0a7dd29d7ffcab0eafcdfb8ebcc0..ecfbc28370b567f327fc4d4a199de3a2de8c46a1 100644 --- a/src/main/webapp/templates/organismList.ftl +++ b/src/main/webapp/templates/organismList.ftl @@ -32,6 +32,7 @@ </#if> <#if organism.organismId?has_content> <a href="/organism?action=listChildOrganisms&organismId=${organism.parentOrganismId!""}" class="btn btn-default" role="button">${i18nBundle.up}</a> + <a href="/organism" class="btn btn-default" role="button">${i18nBundle.toTheTop}</a> </#if> <div class="table-responsive"> <table class="table table-striped"> diff --git a/src/main/webapp/templates/userForm.ftl b/src/main/webapp/templates/userForm.ftl index cbeb2bd76113e7b80a2be79f18e6a860bd1a6942..9add085f9eb3f671fc456024ae3c026a347db5c9 100644 --- a/src/main/webapp/templates/userForm.ftl +++ b/src/main/webapp/templates/userForm.ftl @@ -27,6 +27,7 @@ </script> </#macro> <#macro page_contents> + <p><a href="/user" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> <h1>${viewUser.firstName} ${viewUser.lastName}</h1> <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>> <#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if> diff --git a/src/main/webapp/templates/weatherstation.ftl b/src/main/webapp/templates/weatherstation.ftl deleted file mode 100644 index c28749f5f0426aaec48e12c66659c203181e26c1..0000000000000000000000000000000000000000 --- a/src/main/webapp/templates/weatherstation.ftl +++ /dev/null @@ -1,126 +0,0 @@ -<#-- - Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. - - This file is part of VIPSLogic. - VIPSLogic is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. ---><#include "master.ftl"> -<#macro page_head> - <title>${weatherStation.name}</title> - -</#macro> - -<#macro custom_js> -<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script> -<link href="http://code.jquery.com/ui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" /> -<script type="text/javascript" src="/js/3rdparty/modernizr_custom.js"></script> -<script type="text/javascript"> - // Make sure that there is a date picker present for HTML5 - // date input fields - if (!Modernizr.inputtypes.date) { - $('input[type=date]').datepicker({ dateFormat: 'yy-mm-dd' }); - } -</script> -</#macro> - -<#macro page_contents> - <h1>${weatherStation.name}</h1> - <h2>${i18nBundle.position}</h2> - <ul> - <li>${i18nBundle.latitude}: ${weatherStation.latitude!i18nBundle.missing}</li> - <li>${i18nBundle.longitude}: ${weatherStation.longitude!i18nBundle.missing}</li> - <li>${i18nBundle.altitude}: ${weatherStation.altitude!i18nBundle.missing}</li> - </ul> - <iframe width="300" height="200" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" - src="http://www.openstreetmap.org/export/embed.html?bbox=${(weatherStation.longitude-0.037722588)?c}%2C${(weatherStation.latitude-0.016374048)?c}%2C${(weatherStation.longitude+0.037722588)?c}%2C${(weatherStation.latitude+0.016374048)?c}&layer=mapnik&marker=${weatherStation.latitude?c}%2C${weatherStation.longitude?c}" - style="border: 1px solid black"></iframe><br/> - <a href="http://www.openstreetmap.org/?mlat=${weatherStation.latitude?c}&mlon=${weatherStation.longitude?c}#map=14/${weatherStation.latitude?c}/${weatherStation.longitude?c}&layers=N" target="new">${i18nBundle.viewFullScreen}</a> - <h2>${i18nBundle.dataSource}</h2> - <#if weatherStation.weatherStationDataSourceId??> - <ul> - <li>${i18nBundle.name}: ${weatherStation.weatherStationDataSourceId.name}</li> - <li>URI: <a href="${weatherStation.weatherStationDataSourceId.uri}" target="new">${weatherStation.weatherStationDataSourceId.uri}</a></li> - <li>${i18nBundle.weatherStationRemoteId}: ${weatherStation.weatherStationRemoteId!i18nBundle.weatherStationRemoteIdMissing}</li> - </ul> - <h3>${i18nBundle.test} ${i18nBundle.dataSource?lower_case}</h3> - <form action="${weatherStation.dataFetchUri}" method="POST" target="new" class="form-horizontal" role="form"> - <div class="form-group"> - <label class="col-lg-4 control-label" for="elementMeasurementTypes[]">${i18nBundle.elementMeasurementTypes}</label> - <div class="col-lg-8"> - <select name="elementMeasurementTypes[]" multiple="multiple" cols="10" size="5"> - <option value="TM">TM</option> - <option value="RR">RR</option> - <option value="BT">BT</option> - <option value="Q0">Q0</option> - <option value="UM">UM</option> - </select> - </div> - </div> - <div class="form-group"> - <label class="col-lg-4 control-label" for="logInterval">${i18nBundle.logInterval}</label> - <div class="col-lg-8"> - <select name="logInterval"> - <option value="1h" selected="selected">${i18nBundle.logInterval1h}</option> - <option value="1d">${i18nBundle.logInterval1d}</option> - </select> - </div> - </div> - <div class="form-group"> - <label class="col-lg-4 control-label" for="startDate">${i18nBundle.startTime}</label> - <div class="col-lg-8"> - <input type="date" name="startDate" size="15"/> - <select name="startTime"> - <#list 0..23 as hour> - <option value="${hour?string("00")}"<#if hour == 0> selected="selected"</#if>>${hour?string("00")}</option> - </#list> - </select> - </div> - </div> - <div class="form-group"> - <label class="col-lg-4 control-label" for="endDate">${i18nBundle.endTime}</label> - <div class="col-lg-8"> - <input type="date" name="endDate" size="15"/> - <select name="endTime"> - <#list 0..23 as hour> - <option value="${hour?string("00")}"<#if hour == 0> selected="selected"</#if>>${hour?string("00")}</option> - </#list> - </select> - </div> - </div> - <div class="form-group"> - <label class="col-lg-4 control-label" for="endDate">${i18nBundle.timeZone}</label> - <div class="col-lg-8"> - <select name="timeZone"> - <!-- - <#list -12..12 as offset> - <option value="<#if (offset >= 0)>+</#if>${offset?string("00")}"<#if offset == 0> selected="selected"</#if>><#if (offset >= 0)>+</#if>${offset?string("00")}</option> - </#list> - --> - <#list availableTimeZones as timeZoneId> - <option value="${timeZoneId}"<#if timeZoneId == weatherStation.timeZone> selected="selected"</#if>>${timeZoneId}</option> - </#list> - </select> - </div> - </div> - <div class="form-group"> - <div class="col-lg-offset-4 col-lg-8"> - <button type="submit" class="btn btn-default">Test</button> - </div> - </div> - - </form> - <#else> - <p>${i18nBundle.dataSourceMissing}</p> - </#if> -</#macro> -<@page_html/> diff --git a/src/main/webapp/templates/weatherstationForm.ftl b/src/main/webapp/templates/weatherstationForm.ftl new file mode 100644 index 0000000000000000000000000000000000000000..e3a4ee7120e7f4724fc62d182b32ead845a5af1c --- /dev/null +++ b/src/main/webapp/templates/weatherstationForm.ftl @@ -0,0 +1,143 @@ +<#-- + Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + + This file is part of VIPSLogic. + VIPSLogic is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. +--><#include "master.ftl"> +<#macro page_head> + <title><#if weatherStation.pointOfInterestId?has_content>${i18nBundle.editWeatherStation}<#else>${i18nBundle.newWeatherStation}</#if></title> +</#macro> +<#macro custom_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> + <script type="text/javascript" src="/js/constants.js"></script> + <script type="text/javascript" src="/js/resourcebundle.js"></script> + <script type="text/javascript" src="/js/validateForm.js"></script> + <script type="text/javascript" src="/js/weatherStationFormMap.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + // Load main form definition (for validation) + loadFormDefinition("weatherStationForm"); + + // Initialize the map + // If weather station already registered, center on location + // Otherwise, center and zoom to organizations's default + <#if weatherStation.longitude?has_content && weatherStation.latitude?has_content> + initMap([${(weatherStation.longitude?c)!""},${(weatherStation.latitude?c)!""}],10,${weatherStation.pointOfInterestId!"null"}); + <#else> + initMap([${defaultMapCenter.x?c},${defaultMapCenter.y?c}],${defaultMapZoom},${weatherStation.pointOfInterestId!"null"}); + </#if> + }); + </script> +</#macro> +<#macro page_contents> + <p><a href="/weatherStation" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> + <h1><#if weatherStation.pointOfInterestId?has_content>${i18nBundle.editWeatherStation}<#else>${i18nBundle.newWeatherStation}</#if></h1> + <div id="errorMsgEl" class="alert alert-danger" <#if !formValidation?has_content> style="display:none;"</#if>> + <#if formValidation?has_content>${formValidation.validationMessages?replace("\n", "<br>")}</#if> + </div> + <#if messageKey?has_content> + <div class="alert alert-success">${i18nBundle(messageKey)}</div> + </#if> + <div class="row"> + <div class="col-md-6"> + <#assign formId = "weatherStationForm"> + <form id="${formId}" role="form" action="/weatherStation?action=weatherStationFormSubmit" method="POST" onsubmit="return validateForm(this);"> + <input type="hidden" name="pointOfInterestId" value="${weatherStation.pointOfInterestId!"-1"}"/> + <div class="form-group"> + <label for="name">${i18nBundle.name}</label> + <input type="text" class="form-control" name="name" placeholder="${i18nBundle.name}" value="${(weatherStation.name)!""}" onblur="validateField(this);"/> + <span class="help-block" id="${formId}_name_validation"></span> + </div> + <div class="form-group"> + <label for="location">${i18nBundle.location} (<a href="http://en.wikipedia.org/wiki/World_Geodetic_System#A_new_World_Geodetic_System:_WGS_84" target="new">WGS84</a>: ${i18nBundle.longitude},${i18nBundle.latitude})</label> + <input type="text" class="form-control" id="location" name="location" placeholder="${i18nBundle.location}" value="${(weatherStation.longitude?c)!""},${(weatherStation.latitude?c)!""}" onblur="validateField(this);" onchange="if(validateField(this)){updateMarkerPosition();}" /> + <span class="help-block" id="${formId}_location_validation"></span> + </div> + <div class="form-group"> + <label for="altitude">${i18nBundle.altitude} (${i18nBundle.meter})</label> + <input type="number" class="form-control" name="altitude" placeholder="${i18nBundle.altitude}" value="${(weatherStation.altitude?c)!""}" onblur="validateField(this);"/> + <span class="help-block" id="${formId}_altitude_validation"></span> + </div> + <div class="form-group"> + <label for="weatherStationDataSourceId">${i18nBundle.dataSource}</label> + <select class="form-control" name="weatherStationDataSourceId" onblur="validateField(this);"> + <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.dataSource?lower_case} + <#list dataSources as dataSource> + <option value="${dataSource.weatherStationDataSourceId}" + <#if weatherStation.weatherStationDataSourceId?has_content && weatherStation.weatherStationDataSourceId.weatherStationDataSourceId == dataSource.weatherStationDataSourceId>selected="selected"</#if> + >${dataSource.name}</option> + </#list> + </select> + <span class="help-block" id="${formId}_weatherStationDataSourceId_validation"></span> + </div> + <div class="form-group"> + <label for="weatherStationRemoteId">${i18nBundle.weatherStationRemoteId}</label> + <input type="text" class="form-control" name="weatherStationRemoteId" placeholder="${i18nBundle.weatherStationRemoteId}" value="${(weatherStation.weatherStationRemoteId)!""}" onblur="validateField(this);"/> + <span class="help-block" id="${formId}_weatherStationRemoteId_validation"></span> + </div> + <div class="form-group"> + <label for="timeZone">${i18nBundle.timeZone}</label> + <select class="form-control" name="timeZone" onblur="validateField(this);"> + <#list availableTimeZones as timeZoneId> + <option value="${timeZoneId}"<#if + (weatherStation.timeZone?has_content && timeZoneId == weatherStation.timeZone) + || (!weatherStation.timeZone?has_content && timeZoneId == defaultTimeZoneId) + > selected="selected"</#if>>${timeZoneId}</option> + </#list> + </select> + <span class="help-block" id="${formId}_timeZone_validation"></span> + </div> + <div class="form-group"> + <label for="countryCode">${i18nBundle.country}</label> + <select class="form-control" name="countryCode" onblur="validateField(this);"> + <#list availableCountries as country> + <option value="${country.countryCode}"<#if + (weatherStation.countryCode?has_content && country.countryCode == weatherStation.countryCode.countryCode) + || (!weatherStation.countryCode?has_content && country.countryCode == defaultCountryCode) + > selected="selected"</#if>>${country.getCountryName(currentLocale)}</option> + </#list> + </select> + <span class="help-block" id="${formId}_countryCode_validation"></span> + </div> + <#if user.isSuperUser()> + <div class="form-group"> + <label for="userId">${i18nBundle.vipsLogicUserId}</label> + <select class="form-control" name="userId" onblur="validateField(this);"> + <option value="-1">${i18nBundle.pleaseSelect} ${i18nBundle.vipsLogicUserId?lower_case}</option> + <#list users as user> + <option value="${user.userId}"<#if weatherStation.userId?has_content && user.userId == weatherStation.userId.userId + > selected="selected"</#if>>${user.lastName}, ${user.firstName} (${user.organizationId.organizationName})</option> + </#list> + </select> + <span class="help-block" id="${formId}_userId_validation"></span> + </div> + </#if> + <button type="submit" class="btn btn-default">${i18nBundle.submit}</button> + <#if weatherStation.pointOfInterestId?has_content> + <!--button type="button" class="btn btn-danger" onclick="if(confirm('${i18nBundle.confirmDelete}')){window.location.href='/weatherStation?action=deleteWeatherStation&pointOfInterestId=${weatherStation.pointOfInterestId}';}">${i18nBundle.delete}</button--> + </#if> + </form> + </div> + <div class="col-md-6"> + <div id="weatherStationFormMap" class="map"> + <div id="popover"></div> + </div> + </div> + </div> + <div style="display: none;"><div id="stationMarker" title="Marker"><img src="/public/images/anemometer_mono.png"/></div></div> +</#macro> +<@page_html/> diff --git a/src/main/webapp/templates/weatherstationList.ftl b/src/main/webapp/templates/weatherstationList.ftl new file mode 100644 index 0000000000000000000000000000000000000000..9c01debb6045fd33f492a06a9ac260bc8c97b7ce --- /dev/null +++ b/src/main/webapp/templates/weatherstationList.ftl @@ -0,0 +1,79 @@ +<#-- + Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + + This file is part of VIPSLogic. + VIPSLogic is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. +--><#include "master.ftl"> +<#macro page_head> + <title>${i18nBundle.weatherStations}</title> +</#macro> +<#macro custom_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> + <script type="text/javascript" src="/js/constants.js"></script> + <script type="text/javascript" src="/js/weatherStationListMap.js"></script> + <script type="text/javascript"> + $(document).ready(function() { + initMap([${defaultMapCenter.x?c},${defaultMapCenter.y?c}],${defaultMapZoom}); + }); + </script> +</#macro> +<#macro page_contents> +<h1>${i18nBundle.weatherStations}</h1> +<p> + <a href="/weatherStation?action=newWeatherStationForm" class="btn btn-default" role="button">${i18nBundle.addNew}</a> +</p> +<div class="row"> + <div class="col-md-8"> + <div id="weatherStationListMap" class="map"> + <div id="popover"></div> + </div> + </div> + <div class="col-md-4"> + <div class="table-responsive"> + <table class="table table-striped"> + <thead> + <th>${i18nBundle.name}</th> + <#if user.isOrganizationAdmin() || user.isSuperUser() > + <th></th> + </#if> + <th>${i18nBundle.dataSourceName}</th> + </thead> + <tbody> + <#list weatherStations?sort_by("name") as weatherStation> + <tr> + <td> + <a href="weatherStation?pointOfInterestId=${weatherStation.pointOfInterestId}">${weatherStation.name!""}</a> + </td> + <#if user.isOrganizationAdmin() || user.isSuperUser() > + <td> + <a href="/weatherStation?action=editWeatherStationForm&pointOfInterestId=${weatherStation.pointOfInterestId}" class="btn btn-default" role="button">${i18nBundle.edit}</a> + </td> + </#if> + <td> + <#if weatherStation.weatherStationDataSourceId?has_content> + <a href="${weatherStation.weatherStationDataSourceId.uri}">${weatherStation.weatherStationDataSourceId.name}</a> + </#if> + </td> + </tr> + </#list> + </tbody> + </table> + </div> + </div> +</div> +</#macro> +<@page_html/> diff --git a/src/main/webapp/templates/weatherstationView.ftl b/src/main/webapp/templates/weatherstationView.ftl new file mode 100644 index 0000000000000000000000000000000000000000..028fb7f33f20d70578fb103a446ead5685ecfd6f --- /dev/null +++ b/src/main/webapp/templates/weatherstationView.ftl @@ -0,0 +1,165 @@ +<#-- + Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. + + This file is part of VIPSLogic. + VIPSLogic is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 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 + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. +--><#include "master.ftl"> +<#macro page_head> + <title>${weatherStation.name}</title> + +</#macro> +<#macro custom_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> +<script type="text/javascript" src="/js/constants.js"></script> +<script src="http://code.jquery.com/ui/1.10.3/jquery-ui.min.js"></script> +<link href="http://code.jquery.com/ui/1.10.3/themes/redmond/jquery-ui.css" rel="stylesheet" /> +<script type="text/javascript" src="/js/3rdparty/modernizr_custom.js"></script> +<script type="text/javascript" src="/js/weatherStationViewMap.js"></script> +<script type="text/javascript"> + $(document).ready(function() { + + // Initialize the map + // If weather station already registered, center on location + // Otherwise, center and zoom to organizations's default + <#if weatherStation.longitude?has_content && weatherStation.latitude?has_content> + initMap([${(weatherStation.longitude?c)!""},${(weatherStation.latitude?c)!""}],10,${weatherStation.pointOfInterestId!"null"}); + <#else> + initMap([${defaultMapCenter.x?c},${defaultMapCenter.y?c}],${defaultMapZoom},${weatherStation.pointOfInterestId!"null"}); + </#if> + }); + // Make sure that there is a date picker present for HTML5 + // date input fields + if (!Modernizr.inputtypes.date) { + $('input[type=date]').datepicker({ dateFormat: 'yy-mm-dd' }); + } +</script> +</#macro> + +<#macro page_contents> + <p><a href="/weatherStation" class="btn btn-default back" role="button">${i18nBundle.back}</a></p> + <h1>${weatherStation.name}</h1> + <div class="row"> + <div class="col-md-6"> + <dl class="dl-horizontal"> + <dt>${i18nBundle.location}</dt> + <dd>${(weatherStation.longitude?c)!""},${(weatherStation.latitude?c)!""}</dd> + + <dt>${i18nBundle.altitude} (${i18nBundle.meter})</dt> + <dd>${(weatherStation.altitude?c)!""}</dd> + + <dt>${i18nBundle.dataSource}</dt> + <dd> + <#if weatherStation.weatherStationDataSourceId?has_content> + <a href="${weatherStation.weatherStationDataSourceId.uri}">${weatherStation.weatherStationDataSourceId.name}</a> + <#else> + ${i18nBundle.dataSourceMissing} + </#if> + </dd> + + <dt>${i18nBundle.weatherStationRemoteId}</dt> + <dd>${(weatherStation.weatherStationRemoteId)!i18nBundle.missing}</dd> + + <dt>${i18nBundle.timeZone}</dt> + <dd>${(weatherStation.timeZone)!""}</dd> + + <dt>${i18nBundle.country}</dt> + <dd>${weatherStation.countryCode.getCountryName(currentLocale)}</dd> + + <dt>${i18nBundle.vipsLogicUserId}</dt> + <dd>${user.lastName}, ${user.firstName} (${user.organizationId.organizationName})</dd> + + </dl> + <#if weatherStation.weatherStationDataSourceId?has_content> + <h3>${i18nBundle.test} ${i18nBundle.dataSource?lower_case}</h3> + <form action="${weatherStation.dataFetchUri}" method="POST" target="new" class="form-horizontal" role="form"> + <div class="form-group"> + <label class="col-lg-4 control-label" for="elementMeasurementTypes[]">${i18nBundle.elementMeasurementTypes}</label> + <div class="col-lg-8"> + <select name="elementMeasurementTypes[]" multiple="multiple" cols="10" size="5"> + <option value="TM">TM</option> + <option value="RR">RR</option> + <option value="BT">BT</option> + <option value="Q0">Q0</option> + <option value="UM">UM</option> + </select> + </div> + </div> + <div class="form-group"> + <label class="col-lg-4 control-label" for="logInterval">${i18nBundle.logInterval}</label> + <div class="col-lg-8"> + <select name="logInterval"> + <option value="1h" selected="selected">${i18nBundle.logInterval1h}</option> + <option value="1d">${i18nBundle.logInterval1d}</option> + </select> + </div> + </div> + <div class="form-group"> + <label class="col-lg-4 control-label" for="startDate">${i18nBundle.startTime}</label> + <div class="col-lg-8"> + <input type="date" name="startDate" size="15"/> + <select name="startTime"> + <#list 0..23 as hour> + <option value="${hour?string("00")}"<#if hour == 0> selected="selected"</#if>>${hour?string("00")}</option> + </#list> + </select> + </div> + </div> + <div class="form-group"> + <label class="col-lg-4 control-label" for="endDate">${i18nBundle.endTime}</label> + <div class="col-lg-8"> + <input type="date" name="endDate" size="15"/> + <select name="endTime"> + <#list 0..23 as hour> + <option value="${hour?string("00")}"<#if hour == 0> selected="selected"</#if>>${hour?string("00")}</option> + </#list> + </select> + </div> + </div> + <div class="form-group"> + <label class="col-lg-4 control-label" for="endDate">${i18nBundle.timeZone}</label> + <div class="col-lg-8"> + <select name="timeZone"> + <!-- + <#list -12..12 as offset> + <option value="<#if (offset >= 0)>+</#if>${offset?string("00")}"<#if offset == 0> selected="selected"</#if>><#if (offset >= 0)>+</#if>${offset?string("00")}</option> + </#list> + --> + <#list availableTimeZones as timeZoneId> + <option value="${timeZoneId}"<#if timeZoneId == weatherStation.timeZone> selected="selected"</#if>>${timeZoneId}</option> + </#list> + </select> + </div> + </div> + <div class="form-group"> + <div class="col-lg-offset-4 col-lg-8"> + <button type="submit" class="btn btn-default">Test</button> + </div> + </div> + + </form> + </#if> + </div> + <div class="col-md-6"> + <div id="weatherStationViewMap" class="map"> + </div> + </div> + </div> + + + +</#macro> +<@page_html/> diff --git a/src/main/webapp/templates/weatherstationform.ftl b/src/main/webapp/templates/weatherstationform.ftl deleted file mode 100644 index 51884a95bef7ac6c436719e5ca795db5318338bd..0000000000000000000000000000000000000000 --- a/src/main/webapp/templates/weatherstationform.ftl +++ /dev/null @@ -1,27 +0,0 @@ -<#-- - Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. - - This file is part of VIPSLogic. - VIPSLogic is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. ---><#include "master.ftl"> -<#macro page_head> - <title>${i18nBundle.edit} ${weatherStation.name}</title> - -</#macro> - -<#macro page_contents> - <h1>${i18nBundle.edit} ${weatherStation.name}</h1> - -</#macro> -<@page_html/> diff --git a/src/main/webapp/templates/weatherstationlist.ftl b/src/main/webapp/templates/weatherstationlist.ftl deleted file mode 100644 index bd466d3a95550c0920b4e1327f8ccef766d6cd0d..0000000000000000000000000000000000000000 --- a/src/main/webapp/templates/weatherstationlist.ftl +++ /dev/null @@ -1,29 +0,0 @@ -<#-- - Copyright (c) 2014 Bioforsk <http://www.bioforsk.no/>. - - This file is part of VIPSLogic. - VIPSLogic is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with VIPSLogic. If not, see <http://www.gnu.org/licenses/>. ---><#include "master.ftl"> -<#macro page_head> - <title>${i18nBundle.weatherStations}</title> -</#macro> -<#macro page_contents> - <h1>${i18nBundle.weatherStations}</h1> - <ul> - <#list weatherStations?sort_by("name") as weatherStation> - <li><a href="weatherStation?pointOfInterestId=${weatherStation.pointOfInterestId}">${weatherStation.name}</a><#if weatherStation.weatherStationDataSourceId??>, ${weatherStation.weatherStationDataSourceId.uri!}<#else></#if></li> - </#list> - </ul> -</#macro> -<@page_html/>