/* Copyright (c) 2011 Danish Maritime Authority * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library. If not, see <http://www.gnu.org/licenses/>. */ package dk.dma.ais.abnormal.stat.rest; import com.google.inject.Inject; import com.google.inject.servlet.RequestScoped; import dk.dma.ais.abnormal.stat.db.StatisticDataRepository; import dk.dma.ais.abnormal.stat.db.data.ShipTypeAndSizeStatisticData; import dk.dma.ais.abnormal.stat.db.data.StatisticData; import dk.dma.ais.abnormal.util.Categorizer; import dk.dma.enav.model.geometry.Area; import dk.dma.enav.model.geometry.BoundingBox; import dk.dma.enav.model.geometry.CoordinateSystem; import dk.dma.enav.model.geometry.Position; import dk.dma.enav.model.geometry.grid.Cell; import dk.dma.enav.model.geometry.grid.Grid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; @RequestScoped @Path("/cell") public class CellResource { private static final Logger LOG = LoggerFactory.getLogger(CellResource.class); static { LOG.debug("CellResource loaded."); } { LOG.debug(this.getClass().getSimpleName() + " created (" + this + " )."); } private StatisticDataRepository statisticsRepository; @Inject public CellResource(StatisticDataRepository statisticsRepository) { this.statisticsRepository = statisticsRepository; } @GET @Produces(MediaType.APPLICATION_JSON) public CellsWrapper getCellIdsWithinBoundaries(@QueryParam("north") Double north, @QueryParam("east") Double east, @QueryParam("south") Double south, @QueryParam("west") Double west) { // http://localhost:8080/abnormal/statisticdata/cell?north=55&east=11&south=54.91&west=10.91 LOG.debug("Attempting get id's of cells inside boundary"); if (north == null) { throw new IllegalArgumentException("Missing 'north' parameter"); } if (east == null) { throw new IllegalArgumentException("Missing 'east' parameter"); } if (west == null) { throw new IllegalArgumentException("Missing 'west' parameter"); } if (south == null) { throw new IllegalArgumentException("Missing 'south' parameter"); } if (north <= south) { throw new IllegalArgumentException("'north' parameter must be > 'south' parameter"); } if (east <= west) { throw new IllegalArgumentException("'east' parameter must be > 'west' parameter"); } if (north - south > 0.1000000001) { // throw new IllegalArgumentException("'north' and 'south' parameters must be within 0.1. The current difference is " + (north - south)); } if (east - west > 0.1000000001) { // throw new IllegalArgumentException("'east' and 'west' parameters must be within 0.1. The current difference is " + (east - west)); } Double gridResolution = statisticsRepository.getMetaData().getGridResolution(); LOG.debug("Statistic set uses grid resolution of " + gridResolution); Grid grid = Grid.create(gridResolution); LOG.debug("Created grid with resolution " + gridResolution); LOG.debug("Looking for cells touching area bounded by " + north + " north, " + east + " east, " + south + " south, and " + west + " west."); Position northWest = Position.create(north, west); Position southEast = Position.create(south, east); Area area = BoundingBox.create(northWest, southEast, CoordinateSystem.CARTESIAN); return new CellsWrapper(loadCellsInArea(grid, area)); } private Set<CellWrapper> loadCellsInArea(Grid grid, Area area) { // These are the statistics stored in the data set Set<String> statisticNames = statisticsRepository.getStatisticNames(); // Compute the cells contained inside the area LOG.debug("Computing which cells are in the area."); Set<Cell> cells = grid.getCells(area); LOG.debug("There are " + cells.size() + " cells in the area."); // Container to collect output data Set<CellWrapper> wrappedCells = new LinkedHashSet<>(); // Load statistic data for cells inside the area for (Cell cell : cells) { ArrayList<StatisticData> statisticsArray = new ArrayList<>(); for (String statisticName : statisticNames) { StatisticData statistics = statisticsRepository.getStatisticData(statisticName, cell.getCellId()); if (statistics != null) { statisticsArray.add(statistics); } } // Add the cell to the output only if its has statistical data if (statisticsArray.size() > 0) { BoundingBox boundingBoxOfCell = grid.getBoundingBoxOfCell(cell); CellWrapper wrappedCell = new CellWrapper(cell, boundingBoxOfCell, statisticsArray.toArray(new StatisticData[statisticsArray.size()])); // Add cell to output wrappedCells.add(wrappedCell); } } LOG.debug("There are " + wrappedCells.size() + " cells with statistic data in the area"); return wrappedCells; } /** * Simulate loading of cells from the repository, but actually generate an artificial pattern of cells with data. * This method is not for production use, but intended for test and development only. * * @param grid the grid system to use * @param area the area - not used; included for signature compliance. * @return */ private Set<CellWrapper> loadDummyCells(Grid grid, Area area) { Set<CellWrapper> cells = new LinkedHashSet<>(); for (double lon = 12.0; lon < 12.50; lon += 0.05) { for (double lat = 56.0; lat < 56.50; lat += 0.05) { ShipTypeAndSizeStatisticData statistic1Data = ShipTypeAndSizeStatisticData.create(); statistic1Data.setValue((short) 1, (short) 1, "stat1", (Integer) 7); ShipTypeAndSizeStatisticData statistic2Data = ShipTypeAndSizeStatisticData.create(); statistic2Data.setValue((short) 1, (short) 1, "statA", (Integer) 9); statistic2Data.setValue((short) 1, (short) 2, "statA", (Integer) 8); statistic2Data.setValue((short) 2, (short) 1, "statA", (Integer) 7); Position nw = Position.create(lat+0.02, lon-0.02); Position se = Position.create(lat-0.02, lon+0.02); BoundingBox boundingBoxOfCell = BoundingBox.create(nw, se, CoordinateSystem.CARTESIAN); Cell cell = grid.getCell(lat, lon); CellWrapper cellWrapper = new CellWrapper(cell, boundingBoxOfCell, statistic1Data, statistic2Data); cells.add(cellWrapper); } } return cells; } private class Metadata { Categories categories = new Categories(); public Categories getCategories() { return categories; } } private class Categories { private final Map<Short, String> size = Categorizer.getAllShipSizeCategoryMappings(); private final Map<Short, String> type = Categorizer.getAllShipTypeCategoryMappings(); private final Map<Short, String> cog = Categorizer.getAllCourseOverGroundCategoryMappings(); private final Map<Short, String> sog = Categorizer.getAllSpeedOverGroundCategoryMappings(); public Map<Short, String> getSize() { return size; } public Map<Short, String> getType() { return type; } public Map<Short, String> getCog() { return cog; } public Map<Short, String> getSog() { return sog; } } /** * Wrap some meta information with a set of cells and their statistic data */ private class CellsWrapper { private final Metadata metadata = new Metadata(); private final Set<CellWrapper> cells; public CellsWrapper(Set<CellWrapper> cells) { this.cells = cells; } public Metadata getMetadata() { return metadata; } public Set<CellWrapper> getCells() { return cells; } } /** * Wrap a single cell (geoextent) and its feataure data. */ private class CellWrapper { private final Cell cell; private final double north; private final double east; private final double south; private final double west; private final Set<StatisticData> statistics; public long getCellId() { return cell.getCellId(); } public double getNorth() { return north; } public double getEast() { return east; } public double getSouth() { return south; } public double getWest() { return west; } public Set<StatisticData> getStatistics() { return statistics; } public CellWrapper(Cell cell, BoundingBox boundingBox, StatisticData... statistics) { this.cell = cell; this.north = boundingBox.getMaxLat(); this.east = boundingBox.getMaxLon(); this.south = boundingBox.getMinLat(); this.west = boundingBox.getMinLon(); this.statistics = new HashSet<>(Arrays.asList(statistics)); } } }