Переглянути джерело

[#9192] Implementing a service to export observations.

Lukas Cerny 3 роки тому
батько
коміт
4a75d42e75

+ 105 - 2
src/main/java/cz/hsrs/db/util/ExportUtil.java

@@ -4,11 +4,20 @@ import java.sql.ResultSet;
 import java.sql.ResultSetMetaData;
 import java.sql.SQLException;
 import java.sql.Types;
-import java.util.List;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import cz.hsrs.db.model.Unit;
 import cz.hsrs.db.pool.SQLExecutor;
 
+import static java.util.stream.Collectors.toList;
+
 /**
  * 
  * @author mkepka
@@ -124,7 +133,101 @@ public class ExportUtil {
         }
         return CSV;
     }
-    
+
+    public static String getObservationsBySensorByGroup(long sensorId, int groupId, LocalDateTime fromTime, LocalDateTime toTime, boolean nullable) throws SQLException {
+        List<String> unitIds = UnitUtil.getUnitsBySensorIdByGroup(sensorId, groupId).stream().map(u -> String.valueOf(u.getUnitId())).collect(toList());
+        String unitIdsStr = String.join(",", unitIds);
+        return getObservationsBySensorByUnits(sensorId, unitIdsStr, groupId, fromTime, toTime, nullable);
+    }
+
+    public static String getObservationsBySensorByUnits(long sensorId, String unitIds, int groupId, LocalDateTime fromTime, LocalDateTime toTime, boolean nullable) throws SQLException {
+        // long sensorId, int groupId, String fromTime, String toTime, boolean nullable
+        if (!Pattern.compile("^[\\d,\\s]+$").matcher(unitIds).find()) {
+            throw new SQLException("unit_id does not contain only numbers.");
+        }
+
+        String sqlPositionQuery = String.format("SELECT u_p.unit_id, ST_X(u_p.the_geom) AS lon, ST_Y(u_p.the_geom) AS lat FROM units_positions AS u_p\n" +
+                "JOIN units AS u ON u.unit_id = u_p.unit_id\n" +
+                "WHERE u_p.unit_id IN (%s) AND u.is_mobile = FALSE;", unitIds);
+
+        ResultSet unitRes = SQLExecutor.getInstance().executeQuery(sqlPositionQuery);
+        Map<Long, Location> unitToPosition = parseUnitPosition(unitRes);
+
+        String sqlObsQuery = String.format("SELECT unit_id, time_stamp, observed_value FROM observations\n" +
+                "WHERE unit_id IN (%s) AND sensor_id = %d AND time_stamp >= '%s' AND time_stamp <= '%s' ORDER BY time_stamp;",
+                unitIds, sensorId, fromTime, toTime);
+        ResultSet obsRes = SQLExecutor.getInstance().executeQuery(sqlObsQuery);
+        Map<Long, Map<LocalDateTime, List<Double>>> unitToObs = parseUnitObservation(obsRes);
+
+        StringBuilder csv = new StringBuilder("unit_id;longitude;latitude");
+        LocalDateTime tempTime = LocalDateTime.of(fromTime.toLocalDate(), LocalTime.of(fromTime.getHour(), 0));
+        List<LocalDateTime> timestampColumns = new LinkedList<>();
+        final DateTimeFormatter timestampPattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+        while(tempTime.isBefore(toTime)) {
+            timestampColumns.add(tempTime);
+            csv.append(";").append(tempTime.format(timestampPattern));
+            tempTime = tempTime.plusHours(1);
+        }
+        csv.append("\n");
+
+        for (Map.Entry<Long, Map<LocalDateTime, List<Double>>> unitToEntry : unitToObs.entrySet()) {
+            long unitId = unitToEntry.getKey();
+            Map<LocalDateTime, List<Double>> obsAtTimestamp = unitToEntry.getValue();
+            Location unitLocation = unitToPosition.get(unitId);
+            csv.append(unitId).append(";").append(unitLocation.lon).append(";").append(unitLocation.lat);
+            for (LocalDateTime timestampColumn : timestampColumns) {
+                double val = obsAtTimestamp.getOrDefault(timestampColumn, Collections.emptyList()).stream().mapToDouble(d -> d).average().orElse(Double.NaN);
+                csv.append(";").append(Double.isNaN(val) ? "" : val);
+            }
+            csv.append("\n");
+        }
+
+        return csv.toString();
+    }
+
+    private static Map<Long, Map<LocalDateTime, List<Double>>> parseUnitObservation(ResultSet res) throws SQLException {
+        if (res == null) {
+            throw new SQLException("Can not load observations.");
+        }
+
+        final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ssX");
+        Map<Long, Map<LocalDateTime, List<Double>>> unitToObs =  new HashMap<>();
+        while(res.next()) {
+            long unitId = res.getLong("unit_id");
+            String timestampStr = res.getString("time_stamp");
+            LocalDateTime timestamp = LocalDateTime.parse(timestampStr, formatter);
+            LocalDateTime timestampAtHour = LocalDateTime.of(timestamp.toLocalDate(), LocalTime.of(timestamp.getHour(), 0));
+            double value = res.getDouble("observed_value");
+            unitToObs.computeIfAbsent(unitId, k -> new HashMap<>())
+                    .computeIfAbsent(timestampAtHour, k -> new LinkedList<>())
+                    .add(value);
+        }
+
+        return unitToObs;
+    }
+
+    private static class Location {
+        String lon, lat;
+        public Location(double lon, double lat) {
+            this.lon = lon == 0 ? "" : String.valueOf(lon);
+            this.lat = lat == 0 ? "" : String.valueOf(lat);
+        }
+    }
+
+    private static Map<Long, Location> parseUnitPosition(ResultSet res) throws SQLException {
+        if (res == null) {
+            throw new SQLException("Can not load unit's locations.");
+        }
+        Map<Long, Location> unitToPosition = new HashMap<>();
+        while(res.next()) {
+            long unitId = res.getLong("unit_id");
+            double lon = res.getDouble("lon");
+            double lat = res.getDouble("lat");
+            unitToPosition.put(unitId, new Location(lon, lat));
+        }
+        return unitToPosition;
+    }
+
     /**
      * 
      * @param res

+ 4 - 1
src/main/java/cz/hsrs/rest/ParamsList.java

@@ -32,7 +32,9 @@ public class ParamsList {
 	 */
     public static final String FROM_TIME = "from_time";
     public static final String TO_TIME = "to_time";
-    
+    public static final String MONTH_OF_YEAR = "month_of_year";
+    public static final String YEAR = "year";
+
     /*
      * Unit
      */    
@@ -50,6 +52,7 @@ public class ParamsList {
      * Values for parameters for REST services 
      */
     public static final String CROSS_TAB_STYLE = "crosstab";
+    public static final String REVERSE_TAB_STYLE = "reverse_crosstab";
     public static final String EXPORT_STYLE = "style";
     
 }

+ 56 - 14
src/main/java/cz/hsrs/rest/provider/ObservationRest.java

@@ -4,10 +4,13 @@
 package cz.hsrs.rest.provider;
 
 import java.sql.SQLException;
+import java.time.*;
+import java.time.format.DateTimeFormatter;
 import java.util.logging.Logger;
 
 import javax.naming.AuthenticationException;
 import javax.servlet.http.HttpServletRequest;
+import javax.validation.constraints.NotNull;
 import javax.ws.rs.DefaultValue;
 import javax.ws.rs.GET;
 import javax.ws.rs.Path;
@@ -60,32 +63,71 @@ public class ObservationRest {
             @QueryParam(ParamsList.GROUP_ID) Integer groupId,
             @QueryParam(ParamsList.FROM_TIME) String fromTime,
             @QueryParam(ParamsList.TO_TIME) String toTime,
+            @QueryParam(ParamsList.MONTH_OF_YEAR) Integer monthOfYear,
+            @QueryParam(ParamsList.YEAR) Integer year,
             @QueryParam(ParamsList.EXPORT_STYLE) String exportStyle,
             @DefaultValue("true") @QueryParam("nullable") Boolean nullable,
-            @Context HttpServletRequest req){
+            @Context HttpServletRequest req) {
         try {
             LoginUser loggedUser = AuthUtil.getAuthenticatedLoginUser(req);
+            if(groupId == null) {
+                groupId = UserUtil.getUserGroupId(loggedUser.getUserName());
+            }
+            if (groupId == null) {
+                return Response.status(HttpStatus.ORDINAL_400_Bad_Request)
+                        .entity(String.format("Request does not contain group id. Define: '%s'.", ParamsList.GROUP_ID))
+                        .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
+                        .build();
+            }
+
+            String CSV;
             if(exportStyle.equalsIgnoreCase(ParamsList.CROSS_TAB_STYLE)) {
-                if(groupId == null) {
-                	groupId = UserUtil.getUserGroupId(loggedUser.getUserName());
-                }
-                String CSV;
                 if(unitIds!= null && !unitIds.isEmpty()){
                 	CSV = ExportUtil.getObservationsBySensorByUnitsCross(sensorId, unitIds, groupId, fromTime, toTime, nullable);
                 }
                 else {
                 	CSV = ExportUtil.getObservationsBySensorByGroupCross(sensorId, groupId, fromTime, toTime, nullable);
-                }           
-                return Response.ok()
-                        .entity(CSV)
-                        .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
-                        .build();
+                }
+            } else if(exportStyle.equalsIgnoreCase(ParamsList.REVERSE_TAB_STYLE)) {
+                if (sensorId == null) {
+                    return Response.status(HttpStatus.ORDINAL_400_Bad_Request)
+                            .entity(String.format("Request does not contain sensor id. Define: '%s'.", ParamsList.SENSOR_ID))
+                            .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
+                            .build();
+                }
+
+                LocalDateTime fromTimestamp;
+                LocalDateTime toTimestamp;
+                if (monthOfYear != null && monthOfYear > 0 && year != null && year > 0) {
+                    LocalDate date = LocalDate.of(year, monthOfYear, 1);
+                    fromTimestamp = LocalDateTime.of(date, LocalTime.MIN);
+                    toTimestamp = fromTimestamp.plusMonths(1);
+                } else if (fromTime != null && toTime != null) {
+                    final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+                    fromTimestamp = LocalDateTime.parse(fromTime, formatter);
+                    toTimestamp = LocalDateTime.parse(toTime, formatter);
+                } else {
+                    return Response.status(HttpStatus.ORDINAL_400_Bad_Request)
+                            .entity(String.format("Request does not contain period of time. Define: ('%s' and '%s') or ('%s' and '%s').",
+                                    ParamsList.FROM_TIME, ParamsList.TO_TIME, ParamsList.MONTH_OF_YEAR, ParamsList.YEAR))
+                            .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
+                            .build();
+                }
+
+                if(unitIds != null) {
+                    CSV = ExportUtil.getObservationsBySensorByUnits(sensorId, unitIds, groupId, fromTimestamp, toTimestamp, nullable);
+                } else {
+                    CSV = ExportUtil.getObservationsBySensorByGroup(sensorId, groupId, fromTimestamp, toTimestamp, nullable);
+                }
+
             } else {
-                return Response.ok()
-                        .entity("Empty")
-                        .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
-                        .build();
+                CSV = "Empty";
             }
+
+            return Response.ok()
+                    .entity(CSV)
+                    .header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN)
+                    .build();
             
         } catch (SQLException e) {
             return Response.status(HttpStatus.ORDINAL_500_Internal_Server_Error)