View Javadoc
1   package net.sumaris.core.service.referential;
2   
3   /*-
4    * #%L
5    * SUMARiS:: Core
6    * %%
7    * Copyright (C) 2018 - 2019 SUMARiS Consortium
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  import com.google.common.base.Preconditions;
26  import com.google.common.collect.ImmutableMap;
27  import com.google.common.collect.ImmutableSet;
28  import com.google.common.collect.Lists;
29  import com.google.common.collect.Maps;
30  import com.vividsolutions.jts.geom.MultiPolygon;
31  import net.sumaris.core.dao.referential.ReferentialDao;
32  import net.sumaris.core.dao.referential.ValidityStatusDao;
33  import net.sumaris.core.dao.referential.location.*;
34  import net.sumaris.core.model.referential.ValidityStatus;
35  import net.sumaris.core.model.referential.ValidityStatusEnum;
36  import net.sumaris.core.model.referential.location.*;
37  import net.sumaris.core.util.Beans;
38  import net.sumaris.core.vo.referential.LocationVO;
39  import net.sumaris.core.vo.referential.ReferentialVO;
40  import org.apache.commons.lang3.StringUtils;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  import org.springframework.beans.factory.annotation.Autowired;
44  import org.springframework.core.io.ResourceLoader;
45  import org.springframework.stereotype.Service;
46  
47  import java.io.PrintStream;
48  import java.util.*;
49  import java.util.regex.Pattern;
50  import java.util.stream.Collectors;
51  
52  @Service("locationService")
53  public class LocationServiceImpl implements LocationService{
54  
55      private static final Logger log = LoggerFactory.getLogger(LocationServiceImpl.class);
56  
57      @Autowired
58      protected LocationDao locationDao;
59  
60      @Autowired
61      protected LocationAreaDao locationAreaDao;
62  
63      @Autowired
64      protected ValidityStatusDao validityStatusDao;
65  
66      @Autowired
67      protected LocationLevelDao locationLevelDao;
68  
69      @Autowired
70      protected LocationClassificationDao locationClassificationDao;
71  
72      @Autowired
73      protected ReferentialDao referentialDao;
74  
75      @Autowired
76      private ResourceLoader resourceLoader;
77  
78      @Override
79      public void insertOrUpdateRectangleLocations() {
80  
81          if (log.isInfoEnabled()) {
82              log.info("Checking all statistical rectangles exists...");
83          }
84  
85          // Retrieve location levels
86          Map<String, LocationLevel> locationLevels = createAndGetLocationLevels(ImmutableMap.<String, String>builder()
87                  .put(LocationLevelEnum.RECTANGLE_ICES.getLabel(), "ICES rectangle")
88                  .put(LocationLevelEnum.RECTANGLE_CGPM_GFCM.getLabel(), "CGPM/GFCM rectangle")
89                  .build());
90  
91          LocationLevel icesRectangleLocationLevel = locationLevels.get(LocationLevelEnum.RECTANGLE_ICES.getLabel());
92          Objects.requireNonNull(icesRectangleLocationLevel);
93          LocationLevel cgpmRquareLocationLevel = locationLevels.get(LocationLevelEnum.RECTANGLE_CGPM_GFCM.getLabel());
94          Objects.requireNonNull(cgpmRquareLocationLevel);
95  
96          ValidityStatus validStatus = validityStatusDao.getOne(ValidityStatusEnum.VALID.getId());
97  
98          // Existing rectangles
99          List<LocationVO> existingLocations = locationLevels.values().stream()
100                 .map(level -> locationDao.getByLocationLevel(level.getId()))
101                 .flatMap(list -> list.stream())
102                 .collect(Collectors.toList());
103 
104         Map<String, LocationVO> rectangleByLabelMap = Beans.splitByProperty(existingLocations, Location.Fields.LABEL);
105 
106         int locationInsertCount = 0;
107         Date creationDate = new Date();
108 
109         Set<String> labels = ImmutableSet.<String>builder()
110                 // ICES rectangles
111                 .addAll(Locations.getAllIcesRectangleLabels(resourceLoader, false))
112                 // GCPM/GFCM rectangles
113                 .addAll(Locations.getAllCgpmGfcmRectangleLabels(resourceLoader, false))
114                 .build();
115 
116         if (labels.size() == existingLocations.size()) {
117             log.info(String.format("No missing rectangle detected (%s found)", existingLocations.size()));
118             return;
119         }
120 
121         log.info(String.format("Inserting missing rectangle (%s existing - %s expected)", existingLocations.size(), labels.size()));
122 
123         for (String label: labels) {
124 
125             if (!rectangleByLabelMap.containsKey(label)) {
126                 Locationntial/location/Location.html#Location">Location location = new Location();
127                 location.setLabel(label);
128                 location.setName(label);
129                 location.setCreationDate(creationDate);
130                 if (label.startsWith("M")) {
131                     location.setLocationLevel(cgpmRquareLocationLevel);
132                 }
133                 else {
134                     location.setLocationLevel(icesRectangleLocationLevel);
135                 }
136                 location.setValidityStatus(validStatus);
137                 locationDao.create(location);
138                 locationInsertCount++;
139             }
140         }
141         if (log.isInfoEnabled()) {
142             log.info(String.format("LOCATION: INSERT count: %s", locationInsertCount));
143         }
144     }
145 
146     @Override
147     public void insertOrUpdateSquares10() {
148         if (log.isInfoEnabled()) {
149             log.info("Checking all squares 10'x10' exists...");
150         }
151 
152         // Retrieve location levels
153         Map<String, LocationLevel> locationLevels = createAndGetLocationLevels(ImmutableMap.<String, String>builder()
154                 .put(LocationLevelEnum.SQUARE_10.getLabel(), "Square 10'x10'")
155                 .build());
156 
157         LocationLevel square10LocationLevel = locationLevels.get(LocationLevelEnum.SQUARE_10.getLabel());
158         Objects.requireNonNull(square10LocationLevel);
159 
160         ValidityStatus validStatus = validityStatusDao.getOne(ValidityStatusEnum.VALID.getId());
161 
162         // Get existing rectangle
163         Map<String, LocationVO> rectangleByLabelMap = Beans.splitByProperty(getExistingRectangles(), Location.Fields.LABEL);
164 
165         // Get existing locations
166         List<LocationVO> existingLocations = locationDao.getByLocationLevel(square10LocationLevel.getId());
167         Map<String, LocationVO> locationByLabelMap = Beans.splitByProperty(existingLocations, Location.Fields.LABEL);
168 
169         int locationInsertCount = 0;
170         int locationAssociationInsertCount = 0;
171         Date creationDate = new Date();
172 
173         Set<String> labels = Locations.getAllSquare10Labels(resourceLoader, false);
174 
175         if (labels.size() == existingLocations.size()) {
176             log.info(String.format("No missing square 10'x10' (%s found)", existingLocations.size()));
177             return;
178         }
179 
180         log.info(String.format("Inserting missing square 10'x10' (%s existing - %s expected)", existingLocations.size(), labels.size()));
181 
182         for (String label: labels) {
183 
184             if (!locationByLabelMap.containsKey(label)) {
185                 Locationntial/location/Location.html#Location">Location location = new Location();
186                 location.setLabel(label);
187                 location.setName(label);
188                 location.setCreationDate(creationDate);
189                 location.setUpdateDate(creationDate);
190                 location.setLocationLevel(square10LocationLevel);
191                 location.setValidityStatus(validStatus);
192                 location = locationDao.create(location);
193                 locationByLabelMap.put(label, locationDao.toLocationVO(location));
194                 locationInsertCount++;
195             }
196         }
197 
198 
199         for (String label: labels) {
200             String parentRectangleLabel = Locations.convertSquare10ToRectangle(label);
201 
202             // Link to parent (rectangle) if exists
203             if (parentRectangleLabel != null) {
204                 LocationVO parentLocation = rectangleByLabelMap.get(parentRectangleLabel);
205                 LocationVO childLocation = locationByLabelMap.get(label);
206 
207                 // Update the square parent, if need:
208                 if (parentLocation != null && !locationDao.hasAssociation(childLocation.getId(), parentLocation.getId())) {
209                     locationDao.addAssociation(childLocation.getId(), parentLocation.getId(), 1d);
210                     locationAssociationInsertCount++;
211                 }
212             }
213         }
214 
215         if (log.isInfoEnabled()) {
216             log.info(String.format("LOCATION: INSERT count: %s", locationInsertCount));
217             log.info(String.format("LOCATION_ASSOCIATION: INSERT count: %s", locationAssociationInsertCount));
218         }
219     }
220 
221     public void insertOrUpdateRectangleAndSquareAreas() {
222 
223         // Retrieve location levels
224         Map<String, LocationLevel> locationLevels = createAndGetLocationLevels(ImmutableMap.<String, String>builder()
225                 .put(LocationLevelEnum.RECTANGLE_ICES.getLabel(), "ICES rectangle")
226                 .put(LocationLevelEnum.RECTANGLE_CGPM_GFCM.getLabel(), "CGPM/GFCM rectangle")
227                 .put(LocationLevelEnum.SQUARE_10.getLabel(), "Square 10' x 10'")
228                 .build());
229         LocationLevel icesRectangleLocationLevel = locationLevels.get(LocationLevelEnum.RECTANGLE_ICES.getLabel());
230         Objects.requireNonNull(icesRectangleLocationLevel);
231         LocationLevel cgpmRectangleLocationLevel = locationLevels.get(LocationLevelEnum.RECTANGLE_CGPM_GFCM.getLabel());
232         Objects.requireNonNull(cgpmRectangleLocationLevel);
233         LocationLevel square10LocationLevel = locationLevels.get(LocationLevelEnum.SQUARE_10.getLabel());
234         Preconditions.checkNotNull(square10LocationLevel);
235 
236         ValidityStatus notValidStatus = validityStatusDao.getOne(ValidityStatusEnum.INVALID.getId());
237         Pattern rectangleLabelPattern = Pattern.compile("[M]?[0-9]{2,3}[A-Z][0-9]");
238 
239         Map<String, LocationVO> rectangleByLabelMap = Maps.newHashMap();
240 
241         int locationInsertCount = 0;
242         int locationUpdateCount = 0;
243         int locationAreaInsertCount = 0;
244         int locationAreaUpdateCount = 0;
245 
246         // Get existing rectangles
247         List<LocationVO> rectangleLocations = getExistingRectangles();
248         List<LocationVO> square10Locations = locationDao.getByLocationLevel(square10LocationLevel.getId());
249 
250         while (rectangleLocations.size() > 0) {
251             for (LocationVO location : rectangleLocations) {
252 
253                 Integer objectId = location.getId();
254                 String rectangleLabel = location.getLabel();
255                 if (!rectangleLabelPattern.matcher(rectangleLabel).matches()) {
256                     log.warn(String.format("Invalid rectangle label {%s.} No geometry will be created for this rectangle.", rectangleLabel));
257                     continue;
258                 }
259                 if (log.isDebugEnabled()) {
260                     log.debug(String.format("Updating geometry of rectangle {%s}", rectangleLabel));
261                 }
262 
263                 // Load rectangle geometry
264                 LocationArea locationArea = locationAreaDao.findById(objectId).orElse(null);
265                 MultiPolygon geometry = (MultiPolygon) Locations.getGeometryFromRectangleLabel(rectangleLabel, true);
266                 Preconditions.checkNotNull(geometry, "No geometry found for rectangle with label:" + rectangleLabel);
267 
268                 if (locationArea == null) {
269                     locationArea = new LocationArea();
270                     locationArea.setId(objectId);
271                     locationArea.setPosition(geometry);
272                     locationAreaDao.save(locationArea);
273                     locationAreaInsertCount++;
274                 } else if (locationArea.getPosition() == null
275                         || !geometry.equals(locationArea.getPosition())) {
276                     locationArea.setPosition(geometry);
277                     locationAreaDao.save(locationArea);
278                     locationAreaUpdateCount++;
279                 }
280 
281                 // Store the rectangle for a later use
282                 rectangleByLabelMap.put(rectangleLabel, location);
283             }
284 
285             // Reset location list (could be fiil if new rectangle are found
286             rectangleLocations = Lists.newArrayList();
287 
288             // Get all squares
289             for (LocationVO location : square10Locations) {
290 
291                 Integer objectId = location.getId();
292                 String squareLabel = location.getLabel();
293                 if (squareLabel == null || squareLabel.length() != 8) {
294                     log.warn(String.format("Invalid square label: %s. No geometry will be created for this square.", squareLabel));
295                     continue;
296                 }
297 
298                 // Load square geometry
299                 LocationArea locationArea = locationAreaDao.getOne(objectId);
300                 MultiPolygon geometry = (MultiPolygon) Locations.getGeometryFromSquare10Label(squareLabel);
301                 Preconditions.checkNotNull(geometry, "No geometry found for square with label:" + squareLabel);
302 
303                 if (locationArea == null) {
304                     locationArea = new LocationArea();
305                     locationArea.setId(objectId);
306                     locationArea.setPosition(geometry);
307                     locationAreaDao.save(locationArea);
308                     locationAreaInsertCount++;
309                 } else if (locationArea.getPosition() == null
310                         || !geometry.equals(locationArea.getPosition())) {
311                     locationArea.setPosition(geometry);
312                     locationAreaDao.save(locationArea);
313                     locationAreaUpdateCount++;
314                 }
315 
316                 // Update parent (as ICES_rectangle) :
317                 String parentRectangleLabel = Locations.convertSquare10ToRectangle(squareLabel);
318                 LocationVO parentLocation = rectangleByLabelMap.get(parentRectangleLabel);
319 
320                 // Create the parent ICES Rectangle if need
321                 if (parentLocation == null) {
322                     //log.debug(String.format("Create rectangle with label %s, because the child square %s has been found.", parentRectangleLabel, location.getLabel()));
323                     parentLocation = new LocationVO();
324                     parentLocation.setLabel(parentRectangleLabel);
325                     parentLocation.setName(parentRectangleLabel);
326                     if (parentRectangleLabel.startsWith("M")) {
327                         parentLocation.setLevelId(cgpmRectangleLocationLevel.getId());
328                     }
329                     else {
330                         parentLocation.setLevelId(icesRectangleLocationLevel.getId());
331                     }
332                     parentLocation.setValidityStatusId(notValidStatus.getId());
333                     parentLocation = (LocationVO)referentialDao.save(parentLocation);
334 
335                     // Add this new rectangle to the list, to enable geometry creation in the next <for> iteration
336                     if (rectangleLocations.contains(parentLocation) == false) {
337                         rectangleLocations.add(parentLocation);
338                         rectangleByLabelMap.put(parentRectangleLabel, parentLocation);
339                     }
340                 }
341 
342                 // Update the square parent, if need:
343                 if (parentLocation != null && !locationDao.hasAssociation(location.getId(), parentLocation.getId())) {
344                     locationDao.addAssociation(location.getId(), parentLocation.getId(), 1d);
345                     locationUpdateCount++;
346                 }
347             }
348 
349             // Reset (to disable <for> in the next <while> iteration)
350             square10Locations = Lists.newArrayList();
351         }
352 
353         if (log.isInfoEnabled()) {
354             log.info(String.format("LOCATION: INSERT count: %s", locationInsertCount));
355             log.info(String.format("          UPDATE count: %s", locationUpdateCount));
356             log.info(String.format("LOCATION_AREA: INSERT count: %s", locationAreaInsertCount));
357             log.info(String.format("               UPDATE count: %s", locationAreaUpdateCount));
358         }
359     }
360 
361     @Override
362     public void updateLocationHierarchy() {
363         if (log.isInfoEnabled()) {
364             log.info("Updating location hierarchy...");
365         }
366         locationDao.updateLocationHierarchy();
367     }
368 
369     @Override
370     public void printLocationPorts(PrintStream out, String indentation) {
371         /*Preconditions.checkArgument(StringUtils.isNotBlank(indentation));
372         Preconditions.checkNotNull(out);
373 
374         Map<String, LocationLevel> locationLevels = createAndGetLocationLevels(ImmutableMap.<String, String>builder()
375                 .put(LocationLevelLabels.HARBOUR, "Port")
376                 .put(LocationLevelLabels.COUNTRY, "Country")
377                 .build());
378 
379         LocationLevel countryLocationLevel = locationLevels.get(LocationLevelLabels.COUNTRY);
380         LocationLevel portLocationLevel = locationLevels.get(LocationLevelLabels.HARBOUR);
381 
382         List<Integer> processedPorts = Lists.newArrayList();
383         List<Location> countries = locationDao.getLocationByLocationLevel(countryLocationLevel.getId());
384         for (Location country: countries) {
385             if ("1".equals(country.getValidityStatus().getCode())) {
386                 out.println(String.format("%s - %s (%s)", country.getLabel(), country.getName(), country.getId()));
387 
388                 List<Location> nuts3list = locationDao.getLocationByLevelAndParent(
389                         nut3LocationLevel.getId(),
390                         country.getId());
391                 for (Location nuts3: nuts3list) {
392                     if ("1".equals(nuts3.getValidityStatus().getCode())) {
393                         out.println(String.format("%s%s - %s (%s)", indentation, nuts3.getLabel(), nuts3.getName(), nuts3.getId()));
394 
395                         List<Location> ports = locationDao.getLocationByLevelAndParent(
396                                 portLocationLevel.getId(),
397                                 nuts3.getId());
398                         for (Location port: ports) {
399                             if ("1".equals(port.getValidityStatus().getCode())) {
400                                 out.println(String.format("%s%s%s - %s (%s)", indentation, indentation, port.getLabel(), port.getName(), port.getId()));
401                                 processedPorts.add(port.getId());
402                             }
403                         }
404                     }
405                 }
406 
407                 // Port sans NUTS 3 :
408                 List<Location> ports = locationDao.getLocationByLevelAndParent(
409                         portLocationLevel.getId(),
410                         country.getId());
411                 boolean firstActivePort = true;
412                 for (Location port: ports) {
413 
414                     if ("1".equals(port.getValidityStatus().getCode())
415                             && !processedPorts.contains(port.getId())) {
416                         if (firstActivePort) {
417                             out.println(String.format("%sSANS NUTS3 (ou sans nuts3 valide):", indentation));
418                             firstActivePort = false;
419                         }
420                         out.println(String.format("%s%s%s - %s (%s)", indentation, indentation, port.getLabel(), port.getName(), port.getId()));
421                         processedPorts.add(port.getId());
422                     }
423                 }
424 
425             }
426         }
427 
428         // Port sans pays :
429         List<Location> ports = locationDao.getLocationByLocationLevel(
430                 portLocationLevel.getId());
431         boolean firstActivePort = true;
432         for (Location port: ports) {
433 
434             if ("1".equals(port.getValidityStatus().getCode())
435                     && !processedPorts.contains(port.getId())) {
436                 if (firstActivePort) {
437                     out.println(String.format("SANS PAYS :", indentation));
438                     out.println(String.format("%sSANS NUTS3 :", indentation));
439                     firstActivePort = false;
440                 }
441                 out.println(String.format("%s%s%s - %s (%s)", indentation, indentation, port.getLabel(), port.getName(), port.getId()));
442                 processedPorts.add(port.getId());
443             }
444         }*/
445     }
446 
447 
448     @Override
449     public String getLocationLabelByLatLong(Number latitude, Number longitude) {
450         if (longitude == null || latitude == null) {
451             throw new IllegalArgumentException("Arguments 'latitude' and 'longitude' should not be null.");
452         }
453 
454         // Try to get a statistical rectangle
455         String rectangleLabel = Locations.getRectangleLabelByLatLong(latitude, longitude);
456         if (StringUtils.isNotBlank(rectangleLabel)) return rectangleLabel;
457 
458         // TODO: get it from spatial query ?
459 
460         // Otherwise, return null
461         return null;
462     }
463 
464     @Override
465     public Integer getLocationIdByLatLong(Number latitude, Number longitude) {
466         String locationLabel = getLocationLabelByLatLong(latitude, longitude);
467         if (locationLabel == null) return null;
468         ReferentialVO location = referentialDao.findByUniqueLabel(Location.class.getSimpleName(), locationLabel);
469         return location == null ? null : location.getId();
470     }
471 
472     @Override
473     @Deprecated
474     public void updateRectanglesAndSquares() {
475         // synonym of:
476         insertOrUpdateRectangleAndSquareAreas();
477     }
478 
479     /* -- protected -- */
480 
481     protected Map<String, LocationLevel> createAndGetLocationLevels(Map<String, String> levels) {
482         Map<String, LocationLevel> result = Maps.newHashMap();
483 
484         Date creationDate = new Date();
485         LocationClassification defaultClassification = locationClassificationDao.getByLabel(LocationClassificationEnum.SEA.getLabel());
486 
487         for (String label: levels.keySet()) {
488             String name = StringUtils.trimToNull(levels.get(label));
489 
490             LocationLevel locationLevel = locationLevelDao.findByLabel(label);
491             if (locationLevel == null) {
492                 if (log.isInfoEnabled()) {
493                     log.info(String.format("Adding new LocationLevel with label {%s}", label));
494                 }
495                 locationLevel = new LocationLevel();
496                 locationLevel.setLabel(label);
497                 locationLevel.setName(name);
498                 locationLevel.setCreationDate(creationDate);
499                 locationLevel.setLocationClassification(defaultClassification);
500                 locationLevel = locationLevelDao.create(locationLevel);
501             }
502             result.put(label, locationLevel);
503         }
504         return result;
505     }
506 
507     protected List<LocationVO> getExistingRectangles() {
508         // Retrieve location levels
509         Map<String, LocationLevel> locationLevels = createAndGetLocationLevels(ImmutableMap.<String, String>builder()
510                 .put(LocationLevelEnum.RECTANGLE_ICES.getLabel(), "ICES rectangle")
511                 .put(LocationLevelEnum.RECTANGLE_CGPM_GFCM.getLabel(), "CGPM/GFCM rectangle")
512                 .build());
513 
514         // Get existing rectangles
515         return locationLevels.values()
516                 .stream()
517                 .map(level -> locationDao.getByLocationLevel(level.getId()))
518                 .flatMap(list -> list.stream())
519                 .collect(Collectors.toList());
520 
521     }
522 }